diff --git a/simplepwa/.vscode/settings.json b/simplepwa/.vscode/settings.json
new file mode 100644
index 0000000..3f20db2
--- /dev/null
+++ b/simplepwa/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "liveServer.settings.port": 5501
+}
diff --git a/simplepwa/Dockerfile b/simplepwa/Dockerfile
new file mode 100644
index 0000000..b22c7ff
--- /dev/null
+++ b/simplepwa/Dockerfile
@@ -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
diff --git a/simplepwa/README.md b/simplepwa/README.md
new file mode 100644
index 0000000..1904def
--- /dev/null
+++ b/simplepwa/README.md
@@ -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!
diff --git a/simplepwa/android-chrome-144x144.png b/simplepwa/android-chrome-144x144.png
new file mode 100644
index 0000000..86cecb4
Binary files /dev/null and b/simplepwa/android-chrome-144x144.png differ
diff --git a/simplepwa/android-chrome-192x192.png b/simplepwa/android-chrome-192x192.png
new file mode 100644
index 0000000..d9ad329
Binary files /dev/null and b/simplepwa/android-chrome-192x192.png differ
diff --git a/simplepwa/android-chrome-256x256.png b/simplepwa/android-chrome-256x256.png
new file mode 100644
index 0000000..26eae2b
Binary files /dev/null and b/simplepwa/android-chrome-256x256.png differ
diff --git a/simplepwa/android-chrome-36x36.png b/simplepwa/android-chrome-36x36.png
new file mode 100644
index 0000000..85fbd08
Binary files /dev/null and b/simplepwa/android-chrome-36x36.png differ
diff --git a/simplepwa/android-chrome-384x384.png b/simplepwa/android-chrome-384x384.png
new file mode 100644
index 0000000..5f76eed
Binary files /dev/null and b/simplepwa/android-chrome-384x384.png differ
diff --git a/simplepwa/android-chrome-48x48.png b/simplepwa/android-chrome-48x48.png
new file mode 100644
index 0000000..a8dacba
Binary files /dev/null and b/simplepwa/android-chrome-48x48.png differ
diff --git a/simplepwa/android-chrome-512x512.png b/simplepwa/android-chrome-512x512.png
new file mode 100644
index 0000000..ee12c2b
Binary files /dev/null and b/simplepwa/android-chrome-512x512.png differ
diff --git a/simplepwa/android-chrome-72x72.png b/simplepwa/android-chrome-72x72.png
new file mode 100644
index 0000000..ea95f9f
Binary files /dev/null and b/simplepwa/android-chrome-72x72.png differ
diff --git a/simplepwa/android-chrome-96x96.png b/simplepwa/android-chrome-96x96.png
new file mode 100644
index 0000000..4b854ce
Binary files /dev/null and b/simplepwa/android-chrome-96x96.png differ
diff --git a/simplepwa/apple-touch-icon-114x114.png b/simplepwa/apple-touch-icon-114x114.png
new file mode 100644
index 0000000..3795318
Binary files /dev/null and b/simplepwa/apple-touch-icon-114x114.png differ
diff --git a/simplepwa/apple-touch-icon-120x120.png b/simplepwa/apple-touch-icon-120x120.png
new file mode 100644
index 0000000..aa0cb21
Binary files /dev/null and b/simplepwa/apple-touch-icon-120x120.png differ
diff --git a/simplepwa/apple-touch-icon-144x144.png b/simplepwa/apple-touch-icon-144x144.png
new file mode 100644
index 0000000..9f19592
Binary files /dev/null and b/simplepwa/apple-touch-icon-144x144.png differ
diff --git a/simplepwa/apple-touch-icon-152x152.png b/simplepwa/apple-touch-icon-152x152.png
new file mode 100644
index 0000000..da4450e
Binary files /dev/null and b/simplepwa/apple-touch-icon-152x152.png differ
diff --git a/simplepwa/apple-touch-icon-180x180.png b/simplepwa/apple-touch-icon-180x180.png
new file mode 100644
index 0000000..1e39b0e
Binary files /dev/null and b/simplepwa/apple-touch-icon-180x180.png differ
diff --git a/simplepwa/apple-touch-icon-57x57.png b/simplepwa/apple-touch-icon-57x57.png
new file mode 100644
index 0000000..445e91b
Binary files /dev/null and b/simplepwa/apple-touch-icon-57x57.png differ
diff --git a/simplepwa/apple-touch-icon-60x60.png b/simplepwa/apple-touch-icon-60x60.png
new file mode 100644
index 0000000..e4ffb99
Binary files /dev/null and b/simplepwa/apple-touch-icon-60x60.png differ
diff --git a/simplepwa/apple-touch-icon-72x72.png b/simplepwa/apple-touch-icon-72x72.png
new file mode 100644
index 0000000..dfcec29
Binary files /dev/null and b/simplepwa/apple-touch-icon-72x72.png differ
diff --git a/simplepwa/apple-touch-icon-76x76.png b/simplepwa/apple-touch-icon-76x76.png
new file mode 100644
index 0000000..48fd76f
Binary files /dev/null and b/simplepwa/apple-touch-icon-76x76.png differ
diff --git a/simplepwa/apple-touch-icon.png b/simplepwa/apple-touch-icon.png
new file mode 100644
index 0000000..1e39b0e
Binary files /dev/null and b/simplepwa/apple-touch-icon.png differ
diff --git a/simplepwa/browserconfig.xml b/simplepwa/browserconfig.xml
new file mode 100644
index 0000000..a816a20
--- /dev/null
+++ b/simplepwa/browserconfig.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+ #000000
+
+
+
diff --git a/simplepwa/favicon-16x16.png b/simplepwa/favicon-16x16.png
new file mode 100644
index 0000000..9b87e6e
Binary files /dev/null and b/simplepwa/favicon-16x16.png differ
diff --git a/simplepwa/favicon-32x32.png b/simplepwa/favicon-32x32.png
new file mode 100644
index 0000000..ccde164
Binary files /dev/null and b/simplepwa/favicon-32x32.png differ
diff --git a/simplepwa/favicon.ico b/simplepwa/favicon.ico
new file mode 100644
index 0000000..8280837
Binary files /dev/null and b/simplepwa/favicon.ico differ
diff --git a/simplepwa/favicon.png b/simplepwa/favicon.png
new file mode 100644
index 0000000..0ec16de
Binary files /dev/null and b/simplepwa/favicon.png differ
diff --git a/simplepwa/favicon_config.json b/simplepwa/favicon_config.json
new file mode 100644
index 0000000..8854756
--- /dev/null
+++ b/simplepwa/favicon_config.json
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/simplepwa/index.html b/simplepwa/index.html
new file mode 100644
index 0000000..0b88e25
--- /dev/null
+++ b/simplepwa/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Universal NFC
+
+
+
+
+
+
+
+
Universal NFC
+
+
+
+
+
+
+
+
+
diff --git a/simplepwa/logo.png b/simplepwa/logo.png
new file mode 100644
index 0000000..00b1b5a
Binary files /dev/null and b/simplepwa/logo.png differ
diff --git a/simplepwa/main.js b/simplepwa/main.js
new file mode 100644
index 0000000..4190ca9
--- /dev/null
+++ b/simplepwa/main.js
@@ -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();
diff --git a/simplepwa/manifest.json b/simplepwa/manifest.json
new file mode 100644
index 0000000..a7ceecd
--- /dev/null
+++ b/simplepwa/manifest.json
@@ -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"
+ }
+ ]
+}
diff --git a/simplepwa/maskable_icon.png b/simplepwa/maskable_icon.png
new file mode 100644
index 0000000..dd3828b
Binary files /dev/null and b/simplepwa/maskable_icon.png differ
diff --git a/simplepwa/mstile-144x144.png b/simplepwa/mstile-144x144.png
new file mode 100644
index 0000000..86cecb4
Binary files /dev/null and b/simplepwa/mstile-144x144.png differ
diff --git a/simplepwa/mstile-150x150.png b/simplepwa/mstile-150x150.png
new file mode 100644
index 0000000..d54af1b
Binary files /dev/null and b/simplepwa/mstile-150x150.png differ
diff --git a/simplepwa/mstile-310x150.png b/simplepwa/mstile-310x150.png
new file mode 100644
index 0000000..bced8a6
Binary files /dev/null and b/simplepwa/mstile-310x150.png differ
diff --git a/simplepwa/mstile-310x310.png b/simplepwa/mstile-310x310.png
new file mode 100644
index 0000000..d5dfdb9
Binary files /dev/null and b/simplepwa/mstile-310x310.png differ
diff --git a/simplepwa/mstile-70x70.png b/simplepwa/mstile-70x70.png
new file mode 100644
index 0000000..38fa737
Binary files /dev/null and b/simplepwa/mstile-70x70.png differ
diff --git a/simplepwa/robots.txt b/simplepwa/robots.txt
new file mode 100644
index 0000000..6748eb3
--- /dev/null
+++ b/simplepwa/robots.txt
@@ -0,0 +1,5 @@
+User-agent: *
+Disallow: /.git
+Allow: /
+
+Sitemap: https://simplepwa.com/sitemap.xml
\ No newline at end of file
diff --git a/simplepwa/safari-pinned-tab.svg b/simplepwa/safari-pinned-tab.svg
new file mode 100644
index 0000000..fde0bb1
--- /dev/null
+++ b/simplepwa/safari-pinned-tab.svg
@@ -0,0 +1,18 @@
+
diff --git a/simplepwa/share.jpg b/simplepwa/share.jpg
new file mode 100644
index 0000000..82824e7
Binary files /dev/null and b/simplepwa/share.jpg differ
diff --git a/simplepwa/simplepwa.tar b/simplepwa/simplepwa.tar
new file mode 100644
index 0000000..9d53479
Binary files /dev/null and b/simplepwa/simplepwa.tar differ
diff --git a/simplepwa/sitemap.xml b/simplepwa/sitemap.xml
new file mode 100644
index 0000000..87018dd
--- /dev/null
+++ b/simplepwa/sitemap.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ https://simplepwa.com/
+ 2020-08-22
+
+
+
diff --git a/simplepwa/style.css b/simplepwa/style.css
new file mode 100644
index 0000000..5bc6b45
--- /dev/null
+++ b/simplepwa/style.css
@@ -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;
+}
diff --git a/simplepwa/sw.js b/simplepwa/sw.js
new file mode 100644
index 0000000..5c49151
--- /dev/null
+++ b/simplepwa/sw.js
@@ -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))
+ );
+});
diff --git a/simplepwa/universal-nfc/README.md b/simplepwa/universal-nfc/README.md
new file mode 100644
index 0000000..b95bad3
--- /dev/null
+++ b/simplepwa/universal-nfc/README.md
@@ -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.
+
+[](https://www.npmjs.com/package/universal-nfc)
+[](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.
diff --git a/simplepwa/universal-nfc/dist/definitions.d.ts b/simplepwa/universal-nfc/dist/definitions.d.ts
new file mode 100644
index 0000000..18e6b56
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/definitions.d.ts
@@ -0,0 +1,211 @@
+export interface NfcPlugin {
+ /**
+ * Check if NFC is enabled (Android) or available (iOS/Web).
+ */
+ isEnabled(): Promise;
+ /**
+ * Open NFC settings (Android) or app settings (iOS) or shows guidance (Web).
+ */
+ openSettings(): Promise;
+ /**
+ * Register a callback that will be invoked when NFC status changes.
+ */
+ addListener(eventName: "nfcStatusChanged", listenerFunc: (status: NfcStatusChangedEvent) => void): Promise;
+ /**
+ * Start a read session and register a callback that will be invoked when NFC tags are detected.
+ */
+ startScanSession(options?: StartScanSessionOptions): Promise;
+ /**
+ * Stop the current scan session.
+ */
+ stopScanSession(): Promise;
+ /**
+ * Register a callback that will be invoked when NFC tags are detected.
+ */
+ addListener(eventName: "tagDetected", listenerFunc: (tag: TagDetectedEvent) => void): Promise;
+ /**
+ * Write an NDEF message to an NFC tag.
+ */
+ write(options: WriteOptions): Promise;
+ /**
+ * 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;
+ /**
+ * Format an unformatted NFC tag.
+ */
+ format(): Promise;
+ /**
+ * Erase a formatted NFC tag.
+ */
+ erase(): Promise;
+ /**
+ * Transfer data via NFC.
+ * Only available on Android.
+ */
+ share(options: ShareOptions): Promise;
+ /**
+ * Stop sharing data via NFC.
+ * Only available on Android.
+ */
+ stopSharing(): Promise;
+ /**
+ * Remove all listeners for this plugin.
+ */
+ removeAllListeners(): Promise;
+}
+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;
+}
+/**
+ * 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;
+}
diff --git a/simplepwa/universal-nfc/dist/definitions.js b/simplepwa/universal-nfc/dist/definitions.js
new file mode 100644
index 0000000..3ebb549
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/definitions.js
@@ -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 = {}));
diff --git a/simplepwa/universal-nfc/dist/index.d.ts b/simplepwa/universal-nfc/dist/index.d.ts
new file mode 100644
index 0000000..107e901
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/index.d.ts
@@ -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";
diff --git a/simplepwa/universal-nfc/dist/index.js b/simplepwa/universal-nfc/dist/index.js
new file mode 100644
index 0000000..107e901
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/index.js
@@ -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";
diff --git a/simplepwa/universal-nfc/dist/nfc.d.ts b/simplepwa/universal-nfc/dist/nfc.d.ts
new file mode 100644
index 0000000..6b2248b
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/nfc.d.ts
@@ -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;
+ /**
+ * Open NFC settings (Android) or app settings (iOS) or shows guidance (Web).
+ * This helps users enable NFC if it's disabled.
+ */
+ openSettings(): Promise;
+ /**
+ * Start scanning for NFC tags.
+ * @param options Configuration options for the scan session
+ */
+ startScanSession(options?: StartScanSessionOptions): Promise;
+ /**
+ * Stop the current NFC scan session.
+ */
+ stopScanSession(): Promise;
+ /**
+ * Write an NDEF message to an NFC tag.
+ * @param options Object containing the NDEF message to write
+ */
+ write(options: WriteOptions): Promise;
+ /**
+ * Make an NFC tag read-only.
+ * WARNING: This is a permanent operation that cannot be undone.
+ */
+ makeReadOnly(): Promise;
+ /**
+ * Format an NFC tag, erasing its contents and preparing it for writing.
+ */
+ format(): Promise;
+ /**
+ * Erase the contents of an NFC tag.
+ */
+ erase(): Promise;
+ /**
+ * 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;
+ /**
+ * Stop sharing NDEF data via NFC (Android only).
+ */
+ stopSharing(): Promise;
+ /**
+ * 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;
+ addListener(eventName: "tagDetected", listenerFunc: (tag: TagDetectedEvent) => void): Promise;
+ /**
+ * Remove all event listeners registered for this plugin.
+ */
+ removeAllListeners(): Promise;
+ /**
+ * Internal method to notify listeners of events
+ */
+ private notifyListeners;
+}
diff --git a/simplepwa/universal-nfc/dist/nfc.js b/simplepwa/universal-nfc/dist/nfc.js
new file mode 100644
index 0000000..4b376eb
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/nfc.js
@@ -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);
+ }
+ }
+ }
+ }
+}
diff --git a/simplepwa/universal-nfc/dist/simple-nfc.d.ts b/simplepwa/universal-nfc/dist/simple-nfc.d.ts
new file mode 100644
index 0000000..1be9412
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/simple-nfc.d.ts
@@ -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;
+ /**
+ * 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;
+ /**
+ * Stop scanning for NFC tags
+ */
+ stopReading(): Promise;
+ /**
+ * Write a simple text to an NFC tag
+ */
+ writeText(text: string): Promise;
+ /**
+ * Write a URL to an NFC tag
+ */
+ writeUrl(url: string): Promise;
+ /**
+ * Read a simple NFC tag
+ */
+ read(): NFCDefinition;
+}
diff --git a/simplepwa/universal-nfc/dist/simple-nfc.js b/simplepwa/universal-nfc/dist/simple-nfc.js
new file mode 100644
index 0000000..30fcd73
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/simple-nfc.js
@@ -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" };
+ }
+}
diff --git a/simplepwa/universal-nfc/dist/utils.d.ts b/simplepwa/universal-nfc/dist/utils.d.ts
new file mode 100644
index 0000000..c454133
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/utils.d.ts
@@ -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;
diff --git a/simplepwa/universal-nfc/dist/utils.js b/simplepwa/universal-nfc/dist/utils.js
new file mode 100644
index 0000000..8f4c823
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/utils.js
@@ -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();
+}
diff --git a/simplepwa/universal-nfc/dist/web.d.ts b/simplepwa/universal-nfc/dist/web.d.ts
new file mode 100644
index 0000000..77c6baa
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/web.d.ts
@@ -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;
+ openSettings(): Promise;
+ startScanSession(options?: StartScanSessionOptions): Promise;
+ private parseNdefReading;
+ private mapRecordTypeToTnf;
+ stopScanSession(): Promise;
+ write(options: WriteOptions): Promise;
+ makeReadOnly(): Promise;
+ format(): Promise;
+ erase(): Promise;
+ share(options: ShareOptions): Promise;
+ stopSharing(): Promise;
+ addListener(eventName: "nfcStatusChanged" | "tagDetected", listenerFunc: (data: any) => void): Promise;
+ private removeListener;
+ removeAllListeners(): Promise;
+ private createCompatibilityError;
+}
+import { NFCDefinition } from "./definitions";
+export declare class WebNFC {
+ read(): NFCDefinition;
+}
diff --git a/simplepwa/universal-nfc/dist/web.js b/simplepwa/universal-nfc/dist/web.js
new file mode 100644
index 0000000..69f51e6
--- /dev/null
+++ b/simplepwa/universal-nfc/dist/web.js
@@ -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" };
+ }
+}
diff --git a/simplepwa/universal-nfc/package.json b/simplepwa/universal-nfc/package.json
new file mode 100644
index 0000000..6581193
--- /dev/null
+++ b/simplepwa/universal-nfc/package.json
@@ -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"
+ ]
+}