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

NFC Reader

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

Ready to scan: tap an NFC tag against your device

} + + {tagContent && ( +
+

Tag Content:

+

{tagContent}

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