simplepwa implmentation

This commit is contained in:
Mohamad 2025-03-04 20:28:22 +01:00
parent 3bda04ada7
commit 0ff7c427e0
59 changed files with 1971 additions and 0 deletions

3
simplepwa/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}

8
simplepwa/Dockerfile Normal file
View File

@ -0,0 +1,8 @@
# Use the official Nginx image as the base image
FROM nginx:alpine
# Copy the PWA files into the Nginx HTML directory
COPY . /usr/share/nginx/html
# Expose port 80 to serve the PWA
EXPOSE 80

70
simplepwa/README.md Normal file
View File

@ -0,0 +1,70 @@
# Simple PWA
## Simple Progressive Web App (PWA) template
### What is this?
Simple PWA is a Progressive Web App template that provides the minimum file structure needed to create a PWA. These files collectively represent a [reliable](https://web.dev/what-are-pwas/#reliable) and [installable](https://web.dev/what-are-pwas/#installable) web application. It's up to you to add functionality to make it [capable](https://web.dev/what-are-pwas/#capable).
Simple PWA is "offline-first", using a ["cache falling back to the network"](https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker#cache_falling_back_to_the_network) caching strategy, which means any files specified in [`sw.js`](https://github.com/nikkifurls/simple-pwa/blob/master/sw.js#L10-L46) will be cached, and therefore, accessible offline. Non-cached requests (including non-GET requests, as they cannot be cached), will not be accessible offline, and instead, will ping the network or fail if there is no network available. In [`sw.js`](https://github.com/nikkifurls/simple-pwa/blob/master/sw.js), the value of [`cacheName`](https://github.com/nikkifurls/simple-pwa/blob/master/sw.js#L1) should be changed whenever the app is updated in order to force the cache to update from the network.
- `android-chrome-36x36.png` Favicon, Android Chrome M39+ with 0.75 screen density
- `android-chrome-48x48.png` Favicon, Android Chrome M39+ with 1.0 screen density
- `android-chrome-72x72.png` Favicon, Android Chrome M39+ with 1.5 screen density
- `android-chrome-96x96.png` Favicon, Android Chrome M39+ with 2.0 screen density
- `android-chrome-144x144.png` Favicon, Android Chrome M39+ with 3.0 screen density
- `android-chrome-192x192.png` Favicon, Android Chrome M39+ with 4.0 screen density
- `android-chrome-256x256.png` Favicon, Android Chrome M47+ Splash screen with 1.5 screen density
- `android-chrome-384x384.png` Favicon, Android Chrome M47+ Splash screen with 3.0 screen density
- `android-chrome-512x512.png` Favicon, Android Chrome M47+ Splash screen with 4.0 screen density
- `apple-touch-icon.png` Favicon, Apple default
- `apple-touch-icon-57x57.png` Apple iPhone, Non-retina with iOS6 or prior
- `apple-touch-icon-60x60.png` Apple iPhone, Non-retina with iOS7
- `apple-touch-icon-72x72.png` Apple iPad, Non-retina with iOS6 or prior
- `apple-touch-icon-76x76.png` Apple iPad, Non-retina with iOS7
- `apple-touch-icon-114x114.png` Apple iPhone, Retina with iOS6 or prior
- `apple-touch-icon-120x120.png` Apple iPhone, Retina with iOS7
- `apple-touch-icon-144x144.png` Apple iPad, Retina with iOS6 or prior
- `apple-touch-icon-152x152.png` Apple iPad, Retina with iOS7
- `apple-touch-icon-180x180.png` Apple iPhone 6 Plus with iOS8
- `browserconfig.xml` IE11 icon configuration file
- `favicon_config.json` RealFaviconGenerator configuration file
- `favicon.ico` Favicon, IE and fallback for other browsers
- `favicon.png` Favicon generation source image
- `favicon-16x16.png` Favicon, default
- `favicon-32x32.png` Favicon, Safari on Mac OS
- `index.html` Main HTML file
- `logo.png` Logo
- `main.js` Main Javascript file
- `manifest.json` Manifest file
- `maskable_icon.png` Favicon, [maskable](https://web.dev/maskable-icon)
- `mstile-70x70.png` Favicon, Windows 8 / IE11
- `mstile-144x144.png` Favicon, Windows 8 / IE10
- `mstile-150x150.png` Favicon, Windows 8 / IE11
- `mstile-310x150.png` Favicon, Windows 8 / IE11
- `mstile-310x310.png` Favicon, Windows 8 / IE11
- `README.md` Readme file
- `robots.txt` Robots file
- `safari-pinned-tab.svg` Favicon, Safari pinned tab
- `share.jpg` Social media sharing
- `sitemap.xml` Sitemap file
- `sw.js` Service worker file
- `style.css` Main CSS file
### How do I use it?
1. Clone the repository from [GitHub](https://github.com/nikkifurls/simple-pwa).
2. Create all favicon images using [RealFaviconGenerator](https://realfavicongenerator.net) and replace existing images with generated images.
If you're able to install the CLI version of **RealFaviconGenerator**, `favicon_config.json` contains all settings to generate these images using the following command from the project's root directory. The `real-favicon` tool generates images from `favicon.png`, so replace `favicon.png` prior to running this command. The resulting `favicon_data.json` and `site.webmanifest` can be discarded:
`real-favicon generate favicon_config.json favicon_data.json .`
3. Create new 650x650 maskable icon using [Maskable.app](https://maskable.app) and replace `maskable_icon.png`.
4. Create new black vector icon using [`Manytools' colorize images tool`](http://manytools.org/image/colorize-filter) and replace [`safari-pinned-tab.svg`](https://github.com/nikkifurls/simple-pwa/blob/master/safari-pinned-tab.svg).
5. Create new 1200x630 share image and replace `share.jpg`.
6. Build your PWA by adding HTML, CSS, and Javascript.
### Can I contribute?
YES! Contributions are welcome!

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/mstile-70x70.png"/>
<square150x150logo src="/mstile-150x150.png"/>
<square310x310logo src="/mstile-310x310.png"/>
<wide310x150logo src="/mstile-310x150.png"/>
<TileColor>#000000</TileColor>
</tile>
</msapplication>
</browserconfig>

BIN
simplepwa/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

BIN
simplepwa/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

BIN
simplepwa/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
simplepwa/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,61 @@
{
"masterPicture": "favicon.png",
"iconsPath": "/",
"design": {
"ios": {
"pictureAspect": "backgroundAndMargin",
"backgroundColor": "#000000",
"margin": "18%",
"assets": {
"ios6AndPriorIcons": true,
"ios7AndLaterIcons": true,
"precomposedIcons": false,
"declareOnlyDefaultIcon": true
}
},
"desktopBrowser": {
"design": "raw"
},
"windows": {
"pictureAspect": "noChange",
"backgroundColor": "#000000",
"onConflict": "override",
"assets": {
"windows80Ie10Tile": false,
"windows10Ie11EdgeTiles": {
"small": false,
"medium": true,
"big": false,
"rectangle": false
}
}
},
"androidChrome": {
"pictureAspect": "noChange",
"themeColor": "#000000",
"manifest": {
"display": "standalone",
"orientation": "notSet",
"onConflict": "override",
"declared": true
},
"assets": {
"legacyIcon": false,
"lowResolutionIcons": true
}
},
"safariPinnedTab": {
"pictureAspect": "blackAndWhite",
"threshold": 89.21875,
"themeColor": "#000000"
}
},
"settings": {
"compression": 2,
"scalingAlgorithm": "Mitchell",
"errorOnImageTooSmall": false,
"readmeFile": false,
"htmlCodeFile": false,
"usePathAsIs": false
}
}

23
simplepwa/index.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Universal NFC</title>
<link rel="stylesheet" href="style.css" />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="icons/icon-192x192.png" />
</head>
<body>
<div class="container">
<h2>Universal NFC</h2>
<button id="startReadingBtn">Start Reading</button>
<button id="stopReadingBtn">Stop Reading</button>
<button id="writeTextBtn">Write "Hello NFC"</button>
<button id="writeUrlBtn">Write URL</button>
<div id="output"></div>
</div>
<script type="module" src="main.js"></script>
</body>
</html>

BIN
simplepwa/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

90
simplepwa/main.js Normal file
View File

@ -0,0 +1,90 @@
// Service Worker Registration
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
console.log("ServiceWorker registration successful");
})
.catch((err) => {
console.log("ServiceWorker registration failed: ", err);
});
});
}
import { Nfc, SimpleNfc } from "./universal-nfc/dist/index.js";
// Initialize NFC objects
const nfc = new SimpleNfc();
const NFC = new Nfc();
// DOM Elements
const startReadingBtn = document.getElementById("startReadingBtn");
const stopReadingBtn = document.getElementById("stopReadingBtn");
const writeTextBtn = document.getElementById("writeTextBtn");
const writeUrlBtn = document.getElementById("writeUrlBtn");
const outputDiv = document.getElementById("output");
// Event Listeners
startReadingBtn.addEventListener("click", async () => {
try {
const available = await nfc.isAvailable();
if (!available) {
outputDiv.textContent = "NFC is not available on this device/browser";
return;
}
const { enabled } = await NFC.isEnabled();
if (!enabled) {
outputDiv.textContent = "NFC is not enabled on this device";
return;
}
await nfc.startReading((content, type) => {
outputDiv.textContent = `Read ${type}: ${content}`;
});
} catch (error) {
console.error("Error starting NFC scan:", error);
outputDiv.textContent = `Error: ${error.message}`;
}
});
stopReadingBtn.addEventListener("click", () => {
nfc.stopReading();
outputDiv.textContent = "NFC scanning stopped";
});
writeTextBtn.addEventListener("click", async () => {
try {
const { enabled } = await NFC.isEnabled();
if (!enabled) {
outputDiv.textContent = "NFC is not enabled on this device";
return;
}
await nfc.writeText("Hello NFC");
outputDiv.textContent = "Text written to NFC tag";
} catch (error) {
console.error("Error writing to tag:", error);
outputDiv.textContent = `Error: ${error.message}`;
}
});
writeUrlBtn.addEventListener("click", async () => {
try {
const { enabled } = await NFC.isEnabled();
if (!enabled) {
outputDiv.textContent = "NFC is not enabled on this device";
return;
}
await nfc.writeUrl("https://example.com");
outputDiv.textContent = "URL written to NFC tag";
} catch (error) {
console.error("Error writing URL to tag:", error);
outputDiv.textContent = `Error: ${error.message}`;
}
});
// Initialize camera when page loads
initCamera();

22
simplepwa/manifest.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "Simple PWA",
"short_name": "PWA",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}

BIN
simplepwa/maskable_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
simplepwa/mstile-70x70.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

5
simplepwa/robots.txt Normal file
View File

@ -0,0 +1,5 @@
User-agent: *
Disallow: /.git
Allow: /
Sitemap: https://simplepwa.com/sitemap.xml

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 32 KiB

BIN
simplepwa/share.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
simplepwa/simplepwa.tar Normal file

Binary file not shown.

10
simplepwa/sitemap.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://simplepwa.com/</loc>
<lastmod>2020-08-22</lastmod>
</url>
</urlset>

34
simplepwa/style.css Normal file
View File

@ -0,0 +1,34 @@
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
text-align: center;
}
.container {
background-color: #f4f4f4;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
button {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
margin: 10px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #0056b3;
}
#output {
margin-top: 20px;
font-weight: bold;
min-height: 50px;
background-color: #e9ecef;
padding: 10px;
border-radius: 5px;
}

16
simplepwa/sw.js Normal file
View File

@ -0,0 +1,16 @@
const CACHE_NAME = "simple-pwa-v1";
const urlsToCache = ["/", "/index.html", "/manifest.json"];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(urlsToCache))
);
});
self.addEventListener("fetch", (event) => {
event.respondWith(
caches
.match(event.request)
.then((response) => response || fetch(event.request))
);
});

View File

@ -0,0 +1,444 @@
# 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.

View File

@ -0,0 +1,211 @@
export interface NfcPlugin {
/**
* Check if NFC is enabled (Android) or available (iOS/Web).
*/
isEnabled(): Promise<IsEnabledResult>;
/**
* Open NFC settings (Android) or app settings (iOS) or shows guidance (Web).
*/
openSettings(): Promise<void>;
/**
* Register a callback that will be invoked when NFC status changes.
*/
addListener(eventName: "nfcStatusChanged", listenerFunc: (status: NfcStatusChangedEvent) => void): Promise<PluginListenerHandle>;
/**
* Start a read session and register a callback that will be invoked when NFC tags are detected.
*/
startScanSession(options?: StartScanSessionOptions): Promise<void>;
/**
* Stop the current scan session.
*/
stopScanSession(): Promise<void>;
/**
* Register a callback that will be invoked when NFC tags are detected.
*/
addListener(eventName: "tagDetected", listenerFunc: (tag: TagDetectedEvent) => void): Promise<PluginListenerHandle>;
/**
* Write an NDEF message to an NFC tag.
*/
write(options: WriteOptions): Promise<void>;
/**
* Make an NFC tag read-only. After calling this method, it is no longer possible to write data to the tag.
* BE CAREFUL: This is a one-way process and cannot be undone.
*/
makeReadOnly(): Promise<void>;
/**
* Format an unformatted NFC tag.
*/
format(): Promise<void>;
/**
* Erase a formatted NFC tag.
*/
erase(): Promise<void>;
/**
* Transfer data via NFC.
* Only available on Android.
*/
share(options: ShareOptions): Promise<void>;
/**
* Stop sharing data via NFC.
* Only available on Android.
*/
stopSharing(): Promise<void>;
/**
* Remove all listeners for this plugin.
*/
removeAllListeners(): Promise<void>;
}
export interface IsEnabledResult {
/**
* Whether NFC is enabled or not.
*/
enabled: boolean;
}
export interface NfcStatusChangedEvent {
/**
* Whether NFC was enabled or disabled.
*/
enabled: boolean;
}
export interface StartScanSessionOptions {
/**
* If `true`, the scan session is stopped after the first tag is detected.
* Default: `false`
*/
once?: boolean;
/**
* If `true`, a scan feedback (sound) is played when a tag is detected.
* Only available on iOS.
* Default: `false`
*/
scanSoundEnabled?: boolean;
/**
* If `true`, the scan session is started with alert message.
* Only available on iOS.
* Default: `false`
*/
alertMessageEnabled?: boolean;
}
export interface TagDetectedEvent {
/**
* The ID (serial number) of the tag.
*/
id: string;
/**
* The tech types of the tag (e.g. 'ndef', 'mifare', etc.).
*/
techTypes: string[];
/**
* The NDEF messages contained in the tag.
*/
messages: NdefMessage[];
}
export interface NdefMessage {
/**
* The NDEF records contained in the message.
*/
records: NdefRecord[];
}
export interface NdefRecord {
/**
* The ID of the record. May be empty.
*/
id: string;
/**
* The TNF (Type Name Format) of the record.
* @see NfcTnf for possible values.
*/
tnf: number;
/**
* The type of the record (e.g. 'T' for text, 'U' for URI).
*/
type: string;
/**
* The payload of the record as string, if available.
*/
payload?: string;
/**
* The language code of the record (for text records).
* Only consistently available on Android.
*/
languageCode?: string;
/**
* The URI of the record (for URI records).
*/
uri?: string;
/**
* The text content of the record (for text records).
*/
text?: string;
}
export interface WriteOptions {
/**
* The NDEF message to write.
*/
message: NdefMessage;
}
export interface ShareOptions {
/**
* The NDEF message to share.
*/
message: NdefMessage;
}
export interface PluginListenerHandle {
/**
* Remove the listener.
*/
remove: () => Promise<void>;
}
/**
* NFC TNF (Type Name Format) Constants
* These determine how to interpret the type field.
*/
export declare enum NfcTnf {
EMPTY = 0,
WELL_KNOWN = 1,
MIME_MEDIA = 2,
ABSOLUTE_URI = 3,
EXTERNAL_TYPE = 4,
UNKNOWN = 5,
UNCHANGED = 6,
RESERVED = 7
}
/**
* NFC RTD (Record Type Definition) Constants
* These are standardized type names for common record types.
*/
export declare class NfcRtd {
static readonly TEXT = "T";
static readonly URI = "U";
static readonly SMART_POSTER = "Sp";
static readonly ALTERNATIVE_CARRIER = "ac";
static readonly HANDOVER_CARRIER = "Hc";
static readonly HANDOVER_REQUEST = "Hr";
static readonly HANDOVER_SELECT = "Hs";
}
/**
* Error codes that might be returned by NFC operations
*/
export declare enum NfcErrorType {
NOT_SUPPORTED = "not_supported",
NOT_ENABLED = "not_enabled",
PERMISSION_DENIED = "permission_denied",
NO_TAG = "no_tag",
TAG_ERROR = "tag_error",
IO_ERROR = "io_error",
TIMEOUT = "timeout",
CANCELLED = "cancelled",
UNEXPECTED_ERROR = "unexpected_error"
}
/**
* Standard error structure for NFC operations
*/
export interface NfcError extends Error {
code: NfcErrorType;
message: string;
detail?: any;
}
export interface NFCDefinition {
id: string;
data: string;
}

View File

@ -0,0 +1,43 @@
/**
* NFC TNF (Type Name Format) Constants
* These determine how to interpret the type field.
*/
export var NfcTnf;
(function (NfcTnf) {
NfcTnf[NfcTnf["EMPTY"] = 0] = "EMPTY";
NfcTnf[NfcTnf["WELL_KNOWN"] = 1] = "WELL_KNOWN";
NfcTnf[NfcTnf["MIME_MEDIA"] = 2] = "MIME_MEDIA";
NfcTnf[NfcTnf["ABSOLUTE_URI"] = 3] = "ABSOLUTE_URI";
NfcTnf[NfcTnf["EXTERNAL_TYPE"] = 4] = "EXTERNAL_TYPE";
NfcTnf[NfcTnf["UNKNOWN"] = 5] = "UNKNOWN";
NfcTnf[NfcTnf["UNCHANGED"] = 6] = "UNCHANGED";
NfcTnf[NfcTnf["RESERVED"] = 7] = "RESERVED";
})(NfcTnf || (NfcTnf = {}));
/**
* NFC RTD (Record Type Definition) Constants
* These are standardized type names for common record types.
*/
export class NfcRtd {
}
NfcRtd.TEXT = "T";
NfcRtd.URI = "U";
NfcRtd.SMART_POSTER = "Sp";
NfcRtd.ALTERNATIVE_CARRIER = "ac";
NfcRtd.HANDOVER_CARRIER = "Hc";
NfcRtd.HANDOVER_REQUEST = "Hr";
NfcRtd.HANDOVER_SELECT = "Hs";
/**
* Error codes that might be returned by NFC operations
*/
export var NfcErrorType;
(function (NfcErrorType) {
NfcErrorType["NOT_SUPPORTED"] = "not_supported";
NfcErrorType["NOT_ENABLED"] = "not_enabled";
NfcErrorType["PERMISSION_DENIED"] = "permission_denied";
NfcErrorType["NO_TAG"] = "no_tag";
NfcErrorType["TAG_ERROR"] = "tag_error";
NfcErrorType["IO_ERROR"] = "io_error";
NfcErrorType["TIMEOUT"] = "timeout";
NfcErrorType["CANCELLED"] = "cancelled";
NfcErrorType["UNEXPECTED_ERROR"] = "unexpected_error";
})(NfcErrorType || (NfcErrorType = {}));

View File

@ -0,0 +1,5 @@
export * from "./definitions.js";
export * from "./web.js";
export * from "./nfc.js";
export * from "./utils.js";
export * from "./simple-nfc.js";

5
simplepwa/universal-nfc/dist/index.js vendored Normal file
View File

@ -0,0 +1,5 @@
export * from "./definitions.js";
export * from "./web.js";
export * from "./nfc.js";
export * from "./utils.js";
export * from "./simple-nfc.js";

76
simplepwa/universal-nfc/dist/nfc.d.ts vendored Normal file
View File

@ -0,0 +1,76 @@
import { IsEnabledResult, NfcStatusChangedEvent, StartScanSessionOptions, TagDetectedEvent, WriteOptions, ShareOptions, PluginListenerHandle } from "./definitions.js";
/**
* Main NFC class that provides access to NFC functionality.
* It automatically chooses the appropriate implementation for the current platform.
*/
export declare class Nfc {
private implementation;
private listeners;
constructor();
/**
* Internal method to monitor NFC status changes
*/
private monitorNfcStatus;
/**
* Check if NFC is enabled (Android) or available (iOS/Web).
* @returns Promise resolving to an object with an `enabled` boolean property
*/
isEnabled(): Promise<IsEnabledResult>;
/**
* Open NFC settings (Android) or app settings (iOS) or shows guidance (Web).
* This helps users enable NFC if it's disabled.
*/
openSettings(): Promise<void>;
/**
* Start scanning for NFC tags.
* @param options Configuration options for the scan session
*/
startScanSession(options?: StartScanSessionOptions): Promise<void>;
/**
* Stop the current NFC scan session.
*/
stopScanSession(): Promise<void>;
/**
* Write an NDEF message to an NFC tag.
* @param options Object containing the NDEF message to write
*/
write(options: WriteOptions): Promise<void>;
/**
* Make an NFC tag read-only.
* WARNING: This is a permanent operation that cannot be undone.
*/
makeReadOnly(): Promise<void>;
/**
* Format an NFC tag, erasing its contents and preparing it for writing.
*/
format(): Promise<void>;
/**
* Erase the contents of an NFC tag.
*/
erase(): Promise<void>;
/**
* Share NDEF data via NFC (Android only, not available on iOS or Web).
* @param options Object containing the NDEF message to share
*/
share(options: ShareOptions): Promise<void>;
/**
* Stop sharing NDEF data via NFC (Android only).
*/
stopSharing(): Promise<void>;
/**
* Register an event listener.
* @param eventName Name of the event to listen for
* @param listenerFunc Callback function to invoke when the event occurs
* @returns A handle that can be used to remove the listener
*/
addListener(eventName: "nfcStatusChanged", listenerFunc: (status: NfcStatusChangedEvent) => void): Promise<PluginListenerHandle>;
addListener(eventName: "tagDetected", listenerFunc: (tag: TagDetectedEvent) => void): Promise<PluginListenerHandle>;
/**
* Remove all event listeners registered for this plugin.
*/
removeAllListeners(): Promise<void>;
/**
* Internal method to notify listeners of events
*/
private notifyListeners;
}

167
simplepwa/universal-nfc/dist/nfc.js vendored Normal file
View File

@ -0,0 +1,167 @@
import { NfcErrorType, } from "./definitions.js";
import { WebNfc } from "./web.js";
/**
* Main NFC class that provides access to NFC functionality.
* It automatically chooses the appropriate implementation for the current platform.
*/
export class Nfc {
constructor() {
this.listeners = new Map();
// Currently we only have the Web implementation
this.implementation = new WebNfc();
// Set up status monitoring to track NFC availability
this.monitorNfcStatus();
}
/**
* Internal method to monitor NFC status changes
*/
async monitorNfcStatus() {
try {
// Set up listener for NFC status changes from implementation
await this.implementation.addListener("nfcStatusChanged", (status) => {
// Propagate to our own listeners
this.notifyListeners("nfcStatusChanged", status);
});
}
catch (error) {
console.warn("Failed to set up NFC status monitoring:", error);
}
}
/**
* Check if NFC is enabled (Android) or available (iOS/Web).
* @returns Promise resolving to an object with an `enabled` boolean property
*/
async isEnabled() {
try {
return await this.implementation.isEnabled();
}
catch (error) {
console.error("Error checking NFC status:", error);
return { enabled: false };
}
}
/**
* Open NFC settings (Android) or app settings (iOS) or shows guidance (Web).
* This helps users enable NFC if it's disabled.
*/
async openSettings() {
return this.implementation.openSettings();
}
/**
* Start scanning for NFC tags.
* @param options Configuration options for the scan session
*/
async startScanSession(options) {
var _a, _b, _c;
try {
return await this.implementation.startScanSession(options);
}
catch (error) {
// Convert to a standardized error object if possible
const nfcError = new Error(error.message || "Failed to start NFC scan");
if (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes("not supported")) ||
error.name === "NotSupportedError") {
nfcError.code = NfcErrorType.NOT_SUPPORTED;
}
else if (((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes("permission")) ||
error.name === "NotAllowedError") {
nfcError.code = NfcErrorType.PERMISSION_DENIED;
}
else if ((_c = error.message) === null || _c === void 0 ? void 0 : _c.includes("not enabled")) {
nfcError.code = NfcErrorType.NOT_ENABLED;
}
else {
nfcError.code = NfcErrorType.UNEXPECTED_ERROR;
}
throw nfcError;
}
}
/**
* Stop the current NFC scan session.
*/
async stopScanSession() {
return this.implementation.stopScanSession();
}
/**
* Write an NDEF message to an NFC tag.
* @param options Object containing the NDEF message to write
*/
async write(options) {
return this.implementation.write(options);
}
/**
* Make an NFC tag read-only.
* WARNING: This is a permanent operation that cannot be undone.
*/
async makeReadOnly() {
return this.implementation.makeReadOnly();
}
/**
* Format an NFC tag, erasing its contents and preparing it for writing.
*/
async format() {
return this.implementation.format();
}
/**
* Erase the contents of an NFC tag.
*/
async erase() {
return this.implementation.erase();
}
/**
* Share NDEF data via NFC (Android only, not available on iOS or Web).
* @param options Object containing the NDEF message to share
*/
async share(options) {
return this.implementation.share(options);
}
/**
* Stop sharing NDEF data via NFC (Android only).
*/
async stopSharing() {
return this.implementation.stopSharing();
}
async addListener(eventName, listenerFunc) {
var _a;
// Add to our internal listener registry
if (!this.listeners.has(eventName)) {
this.listeners.set(eventName, new Set());
}
(_a = this.listeners.get(eventName)) === null || _a === void 0 ? void 0 : _a.add(listenerFunc);
// Register with the implementation
const handle = await this.implementation.addListener(eventName, listenerFunc);
// Return a handle that will clean up properly
return {
remove: async () => {
var _a;
(_a = this.listeners.get(eventName)) === null || _a === void 0 ? void 0 : _a.delete(listenerFunc);
return handle.remove();
},
};
}
/**
* Remove all event listeners registered for this plugin.
*/
async removeAllListeners() {
// Clear our internal listener registry
this.listeners.clear();
// Remove listeners from the implementation
return this.implementation.removeAllListeners();
}
/**
* Internal method to notify listeners of events
*/
notifyListeners(eventName, data) {
const listeners = this.listeners.get(eventName);
if (listeners) {
for (const listener of listeners) {
try {
listener(data);
}
catch (error) {
console.error(`Error in ${eventName} listener:`, error);
}
}
}
}
}

View File

@ -0,0 +1,34 @@
import { NFCDefinition } from "./definitions.js";
/**
* A simplified API for common NFC reading operations
*/
export declare class SimpleNfc {
private nfc;
private scanCallback;
constructor();
/**
* Check if NFC is available on this device/browser
*/
isAvailable(): Promise<boolean>;
/**
* Start scanning for NFC tags with a simplified callback
* The callback will receive text content and content type ('text', 'url', or 'other')
*/
startReading(callback: (content: string, type: string) => void): Promise<void>;
/**
* Stop scanning for NFC tags
*/
stopReading(): Promise<void>;
/**
* Write a simple text to an NFC tag
*/
writeText(text: string): Promise<void>;
/**
* Write a URL to an NFC tag
*/
writeUrl(url: string): Promise<void>;
/**
* Read a simple NFC tag
*/
read(): NFCDefinition;
}

View File

@ -0,0 +1,93 @@
import { Nfc } from "./nfc.js";
import { NfcUtils } from "./utils.js";
/**
* A simplified API for common NFC reading operations
*/
export class SimpleNfc {
constructor() {
this.scanCallback = null;
this.nfc = new Nfc();
}
/**
* Check if NFC is available on this device/browser
*/
async isAvailable() {
try {
const result = await this.nfc.isEnabled();
return result.enabled;
}
catch (e) {
return false;
}
}
/**
* Start scanning for NFC tags with a simplified callback
* The callback will receive text content and content type ('text', 'url', or 'other')
*/
async startReading(callback) {
this.scanCallback = callback;
// Set up the tag detection listener
await this.nfc.addListener("tagDetected", (tag) => {
var _a, _b, _c, _d;
// Try to get URL first
const url = NfcUtils.getUrlFromTag(tag);
if (url) {
(_a = this.scanCallback) === null || _a === void 0 ? void 0 : _a.call(this, url, "url");
return;
}
// Then try to get text
const text = NfcUtils.getTextFromTag(tag);
if (text) {
(_b = this.scanCallback) === null || _b === void 0 ? void 0 : _b.call(this, text, "text");
return;
}
// If we got here, we have other content
if (tag.messages.length > 0 && tag.messages[0].records.length > 0) {
const firstRecord = tag.messages[0].records[0];
(_c = this.scanCallback) === null || _c === void 0 ? void 0 : _c.call(this, firstRecord.payload || "Unknown content", "other");
}
else {
(_d = this.scanCallback) === null || _d === void 0 ? void 0 : _d.call(this, "Empty tag", "other");
}
});
// Start the actual scan
await this.nfc.startScanSession();
}
/**
* Stop scanning for NFC tags
*/
async stopReading() {
this.scanCallback = null;
await this.nfc.stopScanSession();
await this.nfc.removeAllListeners();
}
/**
* Write a simple text to an NFC tag
*/
async writeText(text) {
const textRecord = NfcUtils.createTextRecord(text);
await this.nfc.write({
message: {
records: [textRecord],
},
});
}
/**
* Write a URL to an NFC tag
*/
async writeUrl(url) {
const urlRecord = NfcUtils.createUriRecord(url);
await this.nfc.write({
message: {
records: [urlRecord],
},
});
}
/**
* Read a simple NFC tag
*/
read() {
// Dummy implementation
return { id: "2", data: "simple data" };
}
}

39
simplepwa/universal-nfc/dist/utils.d.ts vendored Normal file
View File

@ -0,0 +1,39 @@
import { NdefRecord, NdefMessage } from "./definitions.js";
export declare class NfcUtils {
/**
* Creates a simple text record
*/
static createTextRecord(text: string, languageCode?: string): NdefRecord;
/**
* Creates a URI/URL record
*/
static createUriRecord(uri: string): NdefRecord;
/**
* Creates a complete NDEF message with one or more records
*/
static createMessage(records: NdefRecord[]): NdefMessage;
/**
* Extract text content from detected tag
* Returns the first text record found or null if none
*/
static getTextFromTag(tag: {
messages: NdefMessage[];
}): string | null;
/**
* Extract URL/URI from detected tag
* Returns the first URL record found or null if none
*/
static getUrlFromTag(tag: {
messages: NdefMessage[];
}): string | null;
/**
* Checks if this browser environment supports Web NFC
*/
static isWebNfcSupported(): boolean;
/**
* Checks if the device is likely to have NFC hardware
* (Not 100% reliable but useful as a hint)
*/
static isNfcLikelyAvailable(): boolean;
}
export declare function formatData(data: string): string;

95
simplepwa/universal-nfc/dist/utils.js vendored Normal file
View File

@ -0,0 +1,95 @@
// src/utils.ts
import { NfcTnf, NfcRtd } from "./definitions.js";
export class NfcUtils {
/**
* Creates a simple text record
*/
static createTextRecord(text, languageCode = "en") {
return {
id: "",
tnf: NfcTnf.WELL_KNOWN,
type: NfcRtd.TEXT,
payload: text,
languageCode,
text,
};
}
/**
* Creates a URI/URL record
*/
static createUriRecord(uri) {
return {
id: "",
tnf: NfcTnf.WELL_KNOWN,
type: NfcRtd.URI,
payload: uri,
uri,
};
}
/**
* Creates a complete NDEF message with one or more records
*/
static createMessage(records) {
return { records };
}
/**
* Extract text content from detected tag
* Returns the first text record found or null if none
*/
static getTextFromTag(tag) {
for (const message of tag.messages) {
for (const record of message.records) {
// First check the text field
if (record.text) {
return record.text;
}
// Then check for text record type
if ((record.type === "T" || record.type === "text") && record.payload) {
return record.payload;
}
}
}
return null;
}
/**
* Extract URL/URI from detected tag
* Returns the first URL record found or null if none
*/
static getUrlFromTag(tag) {
for (const message of tag.messages) {
for (const record of message.records) {
// First check uri field
if (record.uri) {
return record.uri;
}
// Then check for URI record type
if ((record.type === "U" || record.type === "url") && record.payload) {
return record.payload;
}
}
}
return null;
}
/**
* Checks if this browser environment supports Web NFC
*/
static isWebNfcSupported() {
return typeof window !== "undefined" && "NDEFReader" in window;
}
/**
* Checks if the device is likely to have NFC hardware
* (Not 100% reliable but useful as a hint)
*/
static isNfcLikelyAvailable() {
const ua = navigator.userAgent;
// Most Android devices have NFC these days
if (/android/i.test(ua))
return true;
// iOS detection is tricky as Web NFC isn't available anyway
return false;
}
}
export function formatData(data) {
// Implement your utility function here
return data.trim();
}

30
simplepwa/universal-nfc/dist/web.d.ts vendored Normal file
View File

@ -0,0 +1,30 @@
import { NfcPlugin, IsEnabledResult, StartScanSessionOptions, WriteOptions, ShareOptions, PluginListenerHandle } from "./definitions.js";
export declare class WebNfc implements NfcPlugin {
private scanSessionActive;
private scanOnce;
private listeners;
private ndefReader;
private nfcSupported;
constructor();
private detectNfcSupport;
isEnabled(): Promise<IsEnabledResult>;
openSettings(): Promise<void>;
startScanSession(options?: StartScanSessionOptions): Promise<void>;
private parseNdefReading;
private mapRecordTypeToTnf;
stopScanSession(): Promise<void>;
write(options: WriteOptions): Promise<void>;
makeReadOnly(): Promise<void>;
format(): Promise<void>;
erase(): Promise<void>;
share(options: ShareOptions): Promise<void>;
stopSharing(): Promise<void>;
addListener(eventName: "nfcStatusChanged" | "tagDetected", listenerFunc: (data: any) => void): Promise<PluginListenerHandle>;
private removeListener;
removeAllListeners(): Promise<void>;
private createCompatibilityError;
}
import { NFCDefinition } from "./definitions";
export declare class WebNFC {
read(): NFCDefinition;
}

311
simplepwa/universal-nfc/dist/web.js vendored Normal file
View File

@ -0,0 +1,311 @@
export class WebNfc {
constructor() {
this.scanSessionActive = false;
this.scanOnce = false;
this.listeners = {};
this.ndefReader = null;
this.nfcSupported = false;
this.detectNfcSupport();
}
detectNfcSupport() {
// Check if Web NFC API is available
if (typeof window !== "undefined") {
// Primary check for NDEFReader
this.nfcSupported = "NDEFReader" in window;
// Log platform info for debugging
const userAgent = navigator.userAgent;
const isHttps = window.location.protocol === "https:";
const isPWA = window.matchMedia("(display-mode: standalone)").matches;
console.info("NFC Support Check:", {
supported: this.nfcSupported,
isHttps: isHttps,
isPWA: isPWA,
isAndroid: /android/i.test(userAgent),
isIOS: /iphone|ipad|ipod/i.test(userAgent),
isChrome: /chrome/i.test(userAgent),
});
if (!this.nfcSupported) {
// Provide helpful message about browser compatibility
const reason = !isHttps
? "NFC requires HTTPS"
: !/android/i.test(userAgent)
? "NFC Web API only supported on Android"
: !/chrome/i.test(userAgent)
? "NFC Web API only supported in Chrome-based browsers"
: "This browser does not support the Web NFC API";
console.warn(`Web NFC not available: ${reason}`);
}
}
else {
this.nfcSupported = false;
console.warn("Web NFC not available: Not in browser environment");
}
}
async isEnabled() {
return { enabled: this.nfcSupported };
}
async openSettings() {
if (!this.nfcSupported) {
throw this.createCompatibilityError();
}
console.warn("openSettings: On web, users must enable NFC in device settings manually.");
// Provide instructions based on browser detection
if (/android/i.test(navigator.userAgent)) {
alert("Please enable NFC in your device settings: Settings > Connected devices > Connection preferences > NFC");
}
else {
alert("Please ensure NFC is enabled on your device.");
}
}
async startScanSession(options) {
var _a;
if (!this.nfcSupported) {
throw this.createCompatibilityError();
}
this.scanSessionActive = true;
this.scanOnce = (_a = options === null || options === void 0 ? void 0 : options.once) !== null && _a !== void 0 ? _a : false;
try {
// Create and configure the NDEF reader
this.ndefReader = new window.NDEFReader();
// Set up event listeners
this.ndefReader.addEventListener("reading", (event) => {
if (!this.scanSessionActive)
return;
try {
const tag = this.parseNdefReading(event);
// Notify listeners
const tagListeners = this.listeners["tagDetected"] || [];
for (const listener of tagListeners) {
listener(tag);
}
// If scanOnce is true, stop scanning after first detection
if (this.scanOnce) {
this.stopScanSession();
}
}
catch (error) {
console.error("Error processing NFC tag:", error);
}
});
this.ndefReader.addEventListener("error", (error) => {
console.error(`NFC Error: ${error.message || error}`);
});
// Start scanning - might throw if user denies permission
await this.ndefReader.scan();
console.log("NFC scan started successfully");
}
catch (error) {
this.scanSessionActive = false;
// Provide user-friendly error message
if (error.name === "NotAllowedError") {
console.error("NFC permission denied by user");
throw new Error("NFC permission denied. Please allow NFC scanning when prompted.");
}
else if (error.name === "NotSupportedError") {
console.error("NFC not supported on this device/browser");
throw new Error("NFC is not supported on this device or browser.");
}
else {
console.error("Error starting NFC scan:", error);
throw new Error(`Failed to start NFC scan: ${error.message || "Unknown error"}`);
}
}
}
parseNdefReading(event) {
const serialNumber = event.serialNumber || "";
const messages = [];
if (event.message) {
const records = [];
// Parse each NDEF record
for (const record of event.message.records) {
const recordType = record.recordType;
let payload = "";
let text = "";
let uri = "";
// Handle different record types
if (record.data) {
const decoder = new TextDecoder();
if (recordType === "text") {
// Text record
text = decoder.decode(record.data);
payload = text;
}
else if (recordType === "url") {
// URL record
uri = decoder.decode(record.data);
payload = uri;
}
else {
// Other record types - try to decode as text
try {
payload = decoder.decode(record.data);
}
catch (e) {
// If text decoding fails, get hex representation
payload = Array.from(new Uint8Array(record.data))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
}
}
// Create record in our format
records.push({
id: "",
tnf: this.mapRecordTypeToTnf(recordType),
type: recordType || "",
payload,
languageCode: record.lang || "en",
text,
uri,
});
}
if (records.length > 0) {
messages.push({ records });
}
}
return {
id: serialNumber,
techTypes: ["ndef"],
messages,
};
}
mapRecordTypeToTnf(recordType) {
// Map Web NFC record types to TNF values
if (!recordType)
return 0; // EMPTY
if (recordType === "text" || recordType === "url")
return 1; // WELL_KNOWN
if (recordType.includes("/"))
return 2; // MIME_MEDIA
if (recordType.startsWith("urn:"))
return 3; // ABSOLUTE_URI
return 4; // EXTERNAL_TYPE (default)
}
async stopScanSession() {
this.scanSessionActive = false;
if (this.ndefReader) {
try {
// While Web NFC doesn't have explicit stop method, we can
// use AbortController in newer implementations
if (this.ndefReader.abort) {
this.ndefReader.abort();
}
console.log("NFC scan stopped");
}
catch (error) {
console.warn("Error stopping NFC scan:", error);
}
}
}
async write(options) {
if (!this.nfcSupported) {
throw this.createCompatibilityError();
}
try {
const writer = new window.NDEFReader();
// Convert our message format to Web NFC format
const records = options.message.records.map((record) => {
const recordOptions = {};
// Handle different record types
if (record.type === "T") {
recordOptions.recordType = "text";
recordOptions.data = record.payload || record.text || "";
if (record.languageCode) {
recordOptions.lang = record.languageCode;
}
}
else if (record.type === "U") {
recordOptions.recordType = "url";
recordOptions.data = record.payload || record.uri || "";
}
else {
// For other types
recordOptions.recordType = record.type;
recordOptions.data = record.payload || "";
}
return recordOptions;
});
// Write the records
await writer.write({ records });
console.log("NFC write successful");
}
catch (error) {
if (error.name === "NotAllowedError") {
throw new Error("NFC write permission denied. Please allow when prompted.");
}
else if (error.name === "NotSupportedError") {
throw new Error("NFC write is not supported on this device or browser.");
}
else {
console.error("Error writing to NFC tag:", error);
throw new Error(`Failed to write to NFC tag: ${error.message || "Unknown error"}`);
}
}
}
async makeReadOnly() {
if (!this.nfcSupported) {
throw this.createCompatibilityError();
}
throw new Error("makeReadOnly is not supported in the Web NFC API");
}
async format() {
if (!this.nfcSupported) {
throw this.createCompatibilityError();
}
// To "format" in Web NFC, write an empty NDEF message
try {
const writer = new window.NDEFReader();
await writer.write({ records: [] });
console.log("NFC tag formatted (wrote empty NDEF message)");
}
catch (error) {
console.error("Error formatting NFC tag:", error);
throw new Error(`Failed to format NFC tag: ${error.message || "Unknown error"}`);
}
}
async erase() {
// Same implementation as format for Web NFC
return this.format();
}
async share(options) {
if (!this.nfcSupported) {
throw this.createCompatibilityError();
}
throw new Error("NFC sharing is not supported in the Web NFC API");
}
async stopSharing() {
if (!this.nfcSupported) {
throw this.createCompatibilityError();
}
throw new Error("NFC sharing is not supported in the Web NFC API");
}
async addListener(eventName, listenerFunc) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName].push(listenerFunc);
return {
remove: async () => {
this.removeListener(eventName, listenerFunc);
},
};
}
removeListener(eventName, listenerFunc) {
if (this.listeners[eventName]) {
this.listeners[eventName] = this.listeners[eventName].filter((listener) => listener !== listenerFunc);
}
}
async removeAllListeners() {
this.listeners = {};
}
createCompatibilityError() {
return new Error("Web NFC API is not supported in this browser. NFC Web API requires Chrome 89+ on Android with NFC hardware, running over HTTPS.");
}
}
export class WebNFC {
// Implement your web NFC functionalities here
read() {
// Dummy implementation
return { id: "1", data: "sample data" };
}
}

View File

@ -0,0 +1,46 @@
{
"name": "universal-nfc",
"version": "1.0.0",
"description": "A framework-agnostic NFC package for reading/writing NFC tags in web apps and PWAs",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"prepare": "npm run build",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"nfc",
"ndef",
"rfid",
"web nfc",
"pwa",
"mobile"
],
"author": "",
"license": "MIT",
"type": "module",
"devDependencies": {
"typescript": "^4.9.5"
},
"repository": {
"type": "git",
"url": "https://github.com/yourusername/universal-nfc.git"
},
"engines": {
"node": ">=12.0.0"
},
"browserslist": [
"chrome >= 89"
],
"files": [
"dist/",
"dist/index.js",
"dist/definitions.js",
"dist/web.js",
"dist/nfc.js",
"dist/simple-nfc.js.js",
"dist/utils.js.js",
"dist/definitions.js"
]
}