# Universal NFC A framework-agnostic NFC package for reading and writing NFC tags in web applications and Progressive Web Apps (PWAs), with iOS support options. 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 - 🍎 iOS support via native bridge integration ## Installation ```bash npm install universal-nfc ``` or ```bash yarn add universal-nfc ``` ## Platform Compatibility | Feature | Chrome for Android (89+) | Chrome for Desktop | Safari iOS | Native iOS Apps | Native Android Apps | Firefox | Edge | |--------------|------------------------|------------------|-----------|----------------|----------------|---------|------| | Reading NFC | ✅ | ❌ | ❌ | ✅* | ✅ | ❌ | ❌ | | Writing NFC | ✅ | ❌ | ❌ | ✅* | ✅ | ❌ | ❌ | \*iOS support requires a native app with an embedded WebView and bridge implementation. **Requirements for Web NFC:** - Chrome 89+ on Android - HTTPS connection (or localhost for development) - Device with NFC hardware - NFC enabled in device settings **Requirements for iOS NFC:** - iOS 11+ device with NFC hardware - Native iOS app with NFC entitlements - Core NFC framework integration - Custom bridge implementation (see iOS Integration section) ## 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'); } ``` ### 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); } } ``` ## iOS Integration ### Overview iOS devices with NFC hardware (iPhone 7 and later running iOS 11+) support reading NFC tags, but with some important restrictions: - **Safari browser cannot directly access NFC**: The Web NFC API is not supported in Safari. - **Native app required**: NFC on iOS requires a native app built with the Core NFC framework. - **Web apps can access NFC through a bridge**: Web content running in a WebView inside a native app can access NFC via a custom bridge. ### iOS Native Bridge Setup To use Universal NFC with iOS, you need to: 1. Create a native iOS app with WebView. 2. Implement Core NFC. 3. Create a JavaScript bridge. #### 1. Native iOS App with NFC Capabilities First, ensure your app has NFC entitlements: - Add the NFC entitlement to your app in Xcode. - Add `NFCReaderUsageDescription` in `Info.plist`. - Enable the "Near Field Communication Tag Reading" capability. #### 2. Implement the NFC Bridge in Swift ```swift import WebKit import CoreNFC class NfcBridge: NSObject, NFCNDEFReaderSessionDelegate { weak var webView: WKWebView? var nfcSession: NFCNDEFReaderSession? init(webView: WKWebView) { self.webView = webView super.init() // Register JavaScript interface let bridgeScript = WKUserScript( source: "window.nativeNfcBridge = { isNfcEnabled: function() { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'isEnabled'}); }, startNfcScan: function(options) { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'startScan', options: options}); }, stopNfcScan: function() { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'stopScan'}); }, writeNfcTag: function(data) { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'writeTag', data: data}); }, openSettings: function() { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'openSettings'}); } };", injectionTime: .atDocumentStart, forMainFrameOnly: false ) let contentController = webView.configuration.userContentController contentController.addUserScript(bridgeScript) contentController.add(self, name: "nfcBridge") } // Handle JavaScript messages func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let body = message.body as? [String: Any], let action = body["action"] as? String else { return } switch action { case "isEnabled": checkNfcAvailability() case "startScan": let options = body["options"] as? String ?? "{}" startNfcSession(options: options) case "stopScan": stopNfcSession() case "writeTag": if let data = body["data"] as? String { writeNfcTag(data: data) } case "openSettings": openSettings() default: break } } private func checkNfcAvailability() { let isAvailable = NFCNDEFReaderSession.readingAvailable sendToWebView(script: "window.dispatchEvent(new MessageEvent('message', {data: {type: 'nfcStatusChanged', enabled: \(isAvailable)}}));") } private func startNfcSession(options: String) { guard NFCNDEFReaderSession.readingAvailable else { sendToWebView(script: "console.error('NFC reading not available on this device');") return } nfcSession = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false) nfcSession?.alertMessage = "Hold your iPhone near an NFC tag" nfcSession?.begin() } private func stopNfcSession() { nfcSession?.invalidate() nfcSession = nil } private func writeNfcTag(data: String) { // Implement write logic } private func openSettings() { if let url = URL(string: UIApplication.openSettingsURLString) { DispatchQueue.main.async { UIApplication.shared.open(url) } } } private func sendToWebView(script: String) { DispatchQueue.main.async { self.webView?.evaluateJavaScript(script, completionHandler: nil) } } } ``` #### 3. Initialize the Bridge in Your View Controller ```swift import UIKit import WebKit class WebViewController: UIViewController { var webView: WKWebView! var nfcBridge: NfcBridge! override func viewDidLoad() { super.viewDidLoad() webView = WKWebView(frame: view.bounds) webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(webView) nfcBridge = NfcBridge(webView: webView) if let url = URL(string: "https://your-web-app-url.com") { webView.load(URLRequest(url: url)) } } } ``` ### Using the iOS Bridge in Your Web App The native bridge will automatically be detected by Universal NFC when your web app runs inside the native iOS app's WebView: ```javascript import { Nfc } from 'universal-nfc'; const nfc = new Nfc(); async function checkNfcSupport() { const { enabled } = await nfc.isEnabled(); console.log('NFC supported and enabled:', enabled); if (enabled) { await nfc.startScanSession(); await nfc.addListener('tagDetected', (tag) => { console.log('NFC tag detected via iOS bridge:', tag); }); } } ``` ### Troubleshooting iOS-Specific Issues #### "NFC reading is not supported in web browsers on iOS" - This error occurs when trying to use NFC in Safari or a standard WebView. - **Solution**: Use the native bridge approach with a custom iOS app. #### "CoreNFC framework missing" - Make sure you have the proper entitlements in your iOS app. - Check that your iOS device supports NFC (iPhone 7 and later). #### "Bridge communication error" - Check the bridge implementation in your native app. - Verify message format passed between WebView and native code. ## 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