# 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<void>` - Open NFC settings or provide guidance
- **startScanSession(options?)**: `Promise<void>` - 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<void>` - Stop scanning for NFC tags
- **write(options)**: `Promise<void>` - Write NDEF data to a tag
- **format()**: `Promise<void>` - Format a tag
- **erase()**: `Promise<void>` - Erase a tag's content
- **makeReadOnly()**: `Promise<void>` - Make a tag read-only (permanent)
- **addListener(eventName, callback)**: `Promise<{remove: () => Promise<void>}>` - Register event listener
- **removeAllListeners()**: `Promise<void>` - 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<boolean>` - Check if NFC is available
- **startReading(callback)**: `Promise<void>` - Start reading tags with a simplified callback
  - `callback(content, type)`: Called with the tag's content and type ('text', 'url', or 'other')
- **stopReading()**: `Promise<void>` - Stop reading tags
- **writeText(text)**: `Promise<void>` - Write text to a tag
- **writeUrl(url)**: `Promise<void>` - 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 (
		<div>
			<h2>NFC Reader</h2>

			{error && <div className="error">{error}</div>}

			<div>
				<button
					onClick={isReading ? stopReading : startReading}
					disabled={!isAvailable}>
					{isReading ? "Stop Reading" : "Start Reading"}
				</button>
			</div>

			{isReading && <p>Ready to scan: tap an NFC tag against your device</p>}

			{tagContent && (
				<div className="tag-content">
					<h3>Tag Content:</h3>
					<p>{tagContent}</p>
				</div>
			)}
		</div>
	);
}

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.
