simplepwa implmentation
3
simplepwa/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"liveServer.settings.port": 5501
|
||||||
|
}
|
8
simplepwa/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Use the official Nginx image as the base image
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy the PWA files into the Nginx HTML directory
|
||||||
|
COPY . /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Expose port 80 to serve the PWA
|
||||||
|
EXPOSE 80
|
70
simplepwa/README.md
Normal file
@ -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!
|
BIN
simplepwa/android-chrome-144x144.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
simplepwa/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
simplepwa/android-chrome-256x256.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
simplepwa/android-chrome-36x36.png
Normal file
After Width: | Height: | Size: 789 B |
BIN
simplepwa/android-chrome-384x384.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
simplepwa/android-chrome-48x48.png
Normal file
After Width: | Height: | Size: 860 B |
BIN
simplepwa/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
simplepwa/android-chrome-72x72.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
simplepwa/android-chrome-96x96.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
simplepwa/apple-touch-icon-114x114.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
simplepwa/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
simplepwa/apple-touch-icon-144x144.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
simplepwa/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
simplepwa/apple-touch-icon-180x180.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
simplepwa/apple-touch-icon-57x57.png
Normal file
After Width: | Height: | Size: 803 B |
BIN
simplepwa/apple-touch-icon-60x60.png
Normal file
After Width: | Height: | Size: 785 B |
BIN
simplepwa/apple-touch-icon-72x72.png
Normal file
After Width: | Height: | Size: 873 B |
BIN
simplepwa/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 857 B |
BIN
simplepwa/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
12
simplepwa/browserconfig.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square70x70logo src="/mstile-70x70.png"/>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<square310x310logo src="/mstile-310x310.png"/>
|
||||||
|
<wide310x150logo src="/mstile-310x150.png"/>
|
||||||
|
<TileColor>#000000</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
BIN
simplepwa/favicon-16x16.png
Normal file
After Width: | Height: | Size: 522 B |
BIN
simplepwa/favicon-32x32.png
Normal file
After Width: | Height: | Size: 710 B |
BIN
simplepwa/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
simplepwa/favicon.png
Normal file
After Width: | Height: | Size: 13 KiB |
61
simplepwa/favicon_config.json
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"masterPicture": "favicon.png",
|
||||||
|
"iconsPath": "/",
|
||||||
|
"design": {
|
||||||
|
"ios": {
|
||||||
|
"pictureAspect": "backgroundAndMargin",
|
||||||
|
"backgroundColor": "#000000",
|
||||||
|
"margin": "18%",
|
||||||
|
"assets": {
|
||||||
|
"ios6AndPriorIcons": true,
|
||||||
|
"ios7AndLaterIcons": true,
|
||||||
|
"precomposedIcons": false,
|
||||||
|
"declareOnlyDefaultIcon": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"desktopBrowser": {
|
||||||
|
"design": "raw"
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"pictureAspect": "noChange",
|
||||||
|
"backgroundColor": "#000000",
|
||||||
|
"onConflict": "override",
|
||||||
|
"assets": {
|
||||||
|
"windows80Ie10Tile": false,
|
||||||
|
"windows10Ie11EdgeTiles": {
|
||||||
|
"small": false,
|
||||||
|
"medium": true,
|
||||||
|
"big": false,
|
||||||
|
"rectangle": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"androidChrome": {
|
||||||
|
"pictureAspect": "noChange",
|
||||||
|
"themeColor": "#000000",
|
||||||
|
"manifest": {
|
||||||
|
"display": "standalone",
|
||||||
|
"orientation": "notSet",
|
||||||
|
"onConflict": "override",
|
||||||
|
"declared": true
|
||||||
|
},
|
||||||
|
"assets": {
|
||||||
|
"legacyIcon": false,
|
||||||
|
"lowResolutionIcons": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safariPinnedTab": {
|
||||||
|
"pictureAspect": "blackAndWhite",
|
||||||
|
"threshold": 89.21875,
|
||||||
|
"themeColor": "#000000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"compression": 2,
|
||||||
|
"scalingAlgorithm": "Mitchell",
|
||||||
|
"errorOnImageTooSmall": false,
|
||||||
|
"readmeFile": false,
|
||||||
|
"htmlCodeFile": false,
|
||||||
|
"usePathAsIs": false
|
||||||
|
}
|
||||||
|
}
|
23
simplepwa/index.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Universal NFC</title>
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<link rel="apple-touch-icon" href="icons/icon-192x192.png" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>Universal NFC</h2>
|
||||||
|
<button id="startReadingBtn">Start Reading</button>
|
||||||
|
<button id="stopReadingBtn">Stop Reading</button>
|
||||||
|
<button id="writeTextBtn">Write "Hello NFC"</button>
|
||||||
|
<button id="writeUrlBtn">Write URL</button>
|
||||||
|
<div id="output"></div>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
simplepwa/logo.png
Normal file
After Width: | Height: | Size: 26 KiB |
90
simplepwa/main.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Service Worker Registration
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register("/sw.js")
|
||||||
|
.then((registration) => {
|
||||||
|
console.log("ServiceWorker registration successful");
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log("ServiceWorker registration failed: ", err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
import { Nfc, SimpleNfc } from "./universal-nfc/dist/index.js";
|
||||||
|
|
||||||
|
// Initialize NFC objects
|
||||||
|
const nfc = new SimpleNfc();
|
||||||
|
const NFC = new Nfc();
|
||||||
|
|
||||||
|
// DOM Elements
|
||||||
|
const startReadingBtn = document.getElementById("startReadingBtn");
|
||||||
|
const stopReadingBtn = document.getElementById("stopReadingBtn");
|
||||||
|
const writeTextBtn = document.getElementById("writeTextBtn");
|
||||||
|
const writeUrlBtn = document.getElementById("writeUrlBtn");
|
||||||
|
const outputDiv = document.getElementById("output");
|
||||||
|
|
||||||
|
// Event Listeners
|
||||||
|
startReadingBtn.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
const available = await nfc.isAvailable();
|
||||||
|
if (!available) {
|
||||||
|
outputDiv.textContent = "NFC is not available on this device/browser";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { enabled } = await NFC.isEnabled();
|
||||||
|
if (!enabled) {
|
||||||
|
outputDiv.textContent = "NFC is not enabled on this device";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await nfc.startReading((content, type) => {
|
||||||
|
outputDiv.textContent = `Read ${type}: ${content}`;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error starting NFC scan:", error);
|
||||||
|
outputDiv.textContent = `Error: ${error.message}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stopReadingBtn.addEventListener("click", () => {
|
||||||
|
nfc.stopReading();
|
||||||
|
outputDiv.textContent = "NFC scanning stopped";
|
||||||
|
});
|
||||||
|
|
||||||
|
writeTextBtn.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
const { enabled } = await NFC.isEnabled();
|
||||||
|
if (!enabled) {
|
||||||
|
outputDiv.textContent = "NFC is not enabled on this device";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await nfc.writeText("Hello NFC");
|
||||||
|
outputDiv.textContent = "Text written to NFC tag";
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error writing to tag:", error);
|
||||||
|
outputDiv.textContent = `Error: ${error.message}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
writeUrlBtn.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
const { enabled } = await NFC.isEnabled();
|
||||||
|
if (!enabled) {
|
||||||
|
outputDiv.textContent = "NFC is not enabled on this device";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await nfc.writeUrl("https://example.com");
|
||||||
|
outputDiv.textContent = "URL written to NFC tag";
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error writing URL to tag:", error);
|
||||||
|
outputDiv.textContent = `Error: ${error.message}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize camera when page loads
|
||||||
|
initCamera();
|
22
simplepwa/manifest.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "Simple PWA",
|
||||||
|
"short_name": "PWA",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/icon-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
simplepwa/maskable_icon.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
simplepwa/mstile-144x144.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
simplepwa/mstile-150x150.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
simplepwa/mstile-310x150.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
simplepwa/mstile-310x310.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
simplepwa/mstile-70x70.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
5
simplepwa/robots.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /.git
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://simplepwa.com/sitemap.xml
|
18
simplepwa/safari-pinned-tab.svg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
simplepwa/share.jpg
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
simplepwa/simplepwa.tar
Normal file
10
simplepwa/sitemap.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
|
||||||
|
<url>
|
||||||
|
<loc>https://simplepwa.com/</loc>
|
||||||
|
<lastmod>2020-08-22</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
</urlset>
|
34
simplepwa/style.css
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
#output {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
min-height: 50px;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
16
simplepwa/sw.js
Normal file
@ -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))
|
||||||
|
);
|
||||||
|
});
|
444
simplepwa/universal-nfc/README.md
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
# Universal NFC
|
||||||
|
|
||||||
|
A framework-agnostic NFC package for reading and writing NFC tags in web applications and Progressive Web Apps (PWAs). This library provides a consistent API for working with NFC across different environments, with a focus on ease of use and developer experience.
|
||||||
|
|
||||||
|
[](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<void>` - Open NFC settings or provide guidance
|
||||||
|
- **startScanSession(options?)**: `Promise<void>` - Start scanning for NFC tags
|
||||||
|
- `options.once`: Stop after first tag (default: false)
|
||||||
|
- `options.scanSoundEnabled`: Play sound on detection (iOS only, default: false)
|
||||||
|
- `options.alertMessageEnabled`: Show alert on start (iOS only, default: false)
|
||||||
|
- **stopScanSession()**: `Promise<void>` - Stop scanning for NFC tags
|
||||||
|
- **write(options)**: `Promise<void>` - Write NDEF data to a tag
|
||||||
|
- **format()**: `Promise<void>` - Format a tag
|
||||||
|
- **erase()**: `Promise<void>` - Erase a tag's content
|
||||||
|
- **makeReadOnly()**: `Promise<void>` - Make a tag read-only (permanent)
|
||||||
|
- **addListener(eventName, callback)**: `Promise<{remove: () => Promise<void>}>` - Register event listener
|
||||||
|
- **removeAllListeners()**: `Promise<void>` - Remove all event listeners
|
||||||
|
|
||||||
|
#### Events
|
||||||
|
|
||||||
|
- **tagDetected**: Fires when an NFC tag is detected
|
||||||
|
- **nfcStatusChanged**: Fires when NFC availability changes
|
||||||
|
|
||||||
|
### Simple API (SimpleNfc class)
|
||||||
|
|
||||||
|
The `SimpleNfc` class provides a simplified interface for common NFC operations.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { SimpleNfc } from "universal-nfc";
|
||||||
|
|
||||||
|
const nfc = new SimpleNfc();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
- **isAvailable()**: `Promise<boolean>` - Check if NFC is available
|
||||||
|
- **startReading(callback)**: `Promise<void>` - Start reading tags with a simplified callback
|
||||||
|
- `callback(content, type)`: Called with the tag's content and type ('text', 'url', or 'other')
|
||||||
|
- **stopReading()**: `Promise<void>` - Stop reading tags
|
||||||
|
- **writeText(text)**: `Promise<void>` - Write text to a tag
|
||||||
|
- **writeUrl(url)**: `Promise<void>` - Write URL to a tag
|
||||||
|
|
||||||
|
### Utilities (NfcUtils)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { NfcUtils } from "universal-nfc";
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
- **createTextRecord(text, languageCode?)**: Create a text NDEF record
|
||||||
|
- **createUriRecord(uri)**: Create a URI NDEF record
|
||||||
|
- **createMessage(records)**: Create an NDEF message from records
|
||||||
|
- **getTextFromTag(tag)**: Extract text content from a tag
|
||||||
|
- **getUrlFromTag(tag)**: Extract URL content from a tag
|
||||||
|
- **isWebNfcSupported()**: Check if Web NFC API is supported
|
||||||
|
- **isNfcLikelyAvailable()**: Check if device likely has NFC hardware
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Using the Core API for Tag Reading
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { Nfc } from "universal-nfc";
|
||||||
|
|
||||||
|
const nfc = new Nfc();
|
||||||
|
|
||||||
|
async function setupNfcReader() {
|
||||||
|
try {
|
||||||
|
const { enabled } = await nfc.isEnabled();
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
console.log("NFC is not enabled or supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register tag detection handler
|
||||||
|
await nfc.addListener("tagDetected", (tag) => {
|
||||||
|
console.log("Tag ID:", tag.id);
|
||||||
|
console.log("Technology types:", tag.techTypes);
|
||||||
|
|
||||||
|
// Process NDEF messages
|
||||||
|
if (tag.messages && tag.messages.length > 0) {
|
||||||
|
for (const message of tag.messages) {
|
||||||
|
for (const record of message.records) {
|
||||||
|
console.log("Record type:", record.type);
|
||||||
|
console.log("Record payload:", record.payload);
|
||||||
|
|
||||||
|
// Handle different record types
|
||||||
|
if (record.type === "T") {
|
||||||
|
console.log("Text:", record.text || record.payload);
|
||||||
|
} else if (record.type === "U") {
|
||||||
|
console.log("URL:", record.uri || record.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start scanning
|
||||||
|
await nfc.startScanSession();
|
||||||
|
console.log("NFC scanning started");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("NFC setup error:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupNfcReader() {
|
||||||
|
nfc
|
||||||
|
.stopScanSession()
|
||||||
|
.then(() => nfc.removeAllListeners())
|
||||||
|
.then(() => {
|
||||||
|
console.log("NFC reader cleaned up");
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Writing Custom Record Types
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { Nfc, NfcTnf, NfcUtils } from "universal-nfc";
|
||||||
|
|
||||||
|
const nfc = new Nfc();
|
||||||
|
|
||||||
|
async function writeCustomData() {
|
||||||
|
// Create a custom record
|
||||||
|
const customRecord = {
|
||||||
|
id: "",
|
||||||
|
tnf: NfcTnf.MIME_MEDIA,
|
||||||
|
type: "application/json",
|
||||||
|
payload: JSON.stringify({ id: 123, name: "Custom Data" }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a message with the custom record and a text record
|
||||||
|
const textRecord = NfcUtils.createTextRecord("This tag contains custom data");
|
||||||
|
const message = NfcUtils.createMessage([textRecord, customRecord]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Write the message to a tag
|
||||||
|
await nfc.write({ message });
|
||||||
|
console.log("Tag written successfully! Tap a tag to write.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error writing tag:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Handling NFC in a React Application
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { SimpleNfc } from "universal-nfc";
|
||||||
|
|
||||||
|
function NfcReader() {
|
||||||
|
const [isReading, setIsReading] = useState(false);
|
||||||
|
const [isAvailable, setIsAvailable] = useState(false);
|
||||||
|
const [tagContent, setTagContent] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const nfcReader = React.useMemo(() => new SimpleNfc(), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if NFC is available when component mounts
|
||||||
|
nfcReader
|
||||||
|
.isAvailable()
|
||||||
|
.then((available) => {
|
||||||
|
setIsAvailable(available);
|
||||||
|
if (!available) {
|
||||||
|
setError("NFC is not available on this device or browser");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setError("Error checking NFC availability: " + err.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup when component unmounts
|
||||||
|
return () => {
|
||||||
|
if (isReading) {
|
||||||
|
nfcReader.stopReading().catch(console.error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [nfcReader]);
|
||||||
|
|
||||||
|
const startReading = async () => {
|
||||||
|
try {
|
||||||
|
setError("");
|
||||||
|
setIsReading(true);
|
||||||
|
|
||||||
|
await nfcReader.startReading((content, type) => {
|
||||||
|
setTagContent(`${type}: ${content}`);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
setError("Error starting NFC: " + err.message);
|
||||||
|
setIsReading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopReading = async () => {
|
||||||
|
try {
|
||||||
|
await nfcReader.stopReading();
|
||||||
|
setIsReading(false);
|
||||||
|
} catch (err) {
|
||||||
|
setError("Error stopping NFC: " + err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>NFC Reader</h2>
|
||||||
|
|
||||||
|
{error && <div className="error">{error}</div>}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={isReading ? stopReading : startReading}
|
||||||
|
disabled={!isAvailable}>
|
||||||
|
{isReading ? "Stop Reading" : "Start Reading"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isReading && <p>Ready to scan: tap an NFC tag against your device</p>}
|
||||||
|
|
||||||
|
{tagContent && (
|
||||||
|
<div className="tag-content">
|
||||||
|
<h3>Tag Content:</h3>
|
||||||
|
<p>{tagContent}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NfcReader;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements for PWA Integration
|
||||||
|
|
||||||
|
To use NFC in a Progressive Web App (PWA):
|
||||||
|
|
||||||
|
1. **HTTPS**: Your app must be served over HTTPS (except for localhost during development)
|
||||||
|
2. **Web App Manifest**: Include an appropriate manifest file:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "NFC Reader App",
|
||||||
|
"short_name": "NFC App",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#4285f4",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Service Worker**: Register a service worker to make your app work offline
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// register-sw.js
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register("/service-worker.js")
|
||||||
|
.then((registration) => {
|
||||||
|
console.log("Service Worker registered");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Service Worker registration failed:", error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Permission Policy**: You may need to include a permission policy header for NFC:
|
||||||
|
|
||||||
|
```
|
||||||
|
Permissions-Policy: nfc=self
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### NFC Not Working
|
||||||
|
|
||||||
|
1. **Check Compatibility**: Ensure you're using Chrome 89+ on Android
|
||||||
|
2. **HTTPS Required**: Make sure your app is served over HTTPS (except on localhost)
|
||||||
|
3. **NFC Hardware**: Verify your device has NFC hardware
|
||||||
|
4. **NFC Enabled**: Ensure NFC is enabled in your device settings
|
||||||
|
5. **Permission**: The user must grant permission when prompted
|
||||||
|
6. **Tag Positioning**: Position the tag correctly against the NFC sensor
|
||||||
|
|
||||||
|
### Common Errors
|
||||||
|
|
||||||
|
- **"NotSupportedError"**: Browser doesn't support Web NFC API
|
||||||
|
- **"NotAllowedError"**: User denied permission or NFC is disabled
|
||||||
|
- **"NetworkError"**: Problem communicating with the NFC tag
|
||||||
|
- **"AbortError"**: The operation was cancelled
|
||||||
|
- **"NotFoundError"**: No NFC tag in range or tag removed too quickly
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This library is inspired by the API design of [@capawesome-team/capacitor-nfc](https://github.com/capawesome-team/capacitor-plugins/tree/main/packages/nfc), but implements a framework-agnostic solution based on the Web NFC API.
|
211
simplepwa/universal-nfc/dist/definitions.d.ts
vendored
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
export interface NfcPlugin {
|
||||||
|
/**
|
||||||
|
* Check if NFC is enabled (Android) or available (iOS/Web).
|
||||||
|
*/
|
||||||
|
isEnabled(): Promise<IsEnabledResult>;
|
||||||
|
/**
|
||||||
|
* Open NFC settings (Android) or app settings (iOS) or shows guidance (Web).
|
||||||
|
*/
|
||||||
|
openSettings(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Register a callback that will be invoked when NFC status changes.
|
||||||
|
*/
|
||||||
|
addListener(eventName: "nfcStatusChanged", listenerFunc: (status: NfcStatusChangedEvent) => void): Promise<PluginListenerHandle>;
|
||||||
|
/**
|
||||||
|
* Start a read session and register a callback that will be invoked when NFC tags are detected.
|
||||||
|
*/
|
||||||
|
startScanSession(options?: StartScanSessionOptions): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Stop the current scan session.
|
||||||
|
*/
|
||||||
|
stopScanSession(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Register a callback that will be invoked when NFC tags are detected.
|
||||||
|
*/
|
||||||
|
addListener(eventName: "tagDetected", listenerFunc: (tag: TagDetectedEvent) => void): Promise<PluginListenerHandle>;
|
||||||
|
/**
|
||||||
|
* Write an NDEF message to an NFC tag.
|
||||||
|
*/
|
||||||
|
write(options: WriteOptions): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Make an NFC tag read-only. After calling this method, it is no longer possible to write data to the tag.
|
||||||
|
* BE CAREFUL: This is a one-way process and cannot be undone.
|
||||||
|
*/
|
||||||
|
makeReadOnly(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Format an unformatted NFC tag.
|
||||||
|
*/
|
||||||
|
format(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Erase a formatted NFC tag.
|
||||||
|
*/
|
||||||
|
erase(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Transfer data via NFC.
|
||||||
|
* Only available on Android.
|
||||||
|
*/
|
||||||
|
share(options: ShareOptions): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Stop sharing data via NFC.
|
||||||
|
* Only available on Android.
|
||||||
|
*/
|
||||||
|
stopSharing(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Remove all listeners for this plugin.
|
||||||
|
*/
|
||||||
|
removeAllListeners(): Promise<void>;
|
||||||
|
}
|
||||||
|
export interface IsEnabledResult {
|
||||||
|
/**
|
||||||
|
* Whether NFC is enabled or not.
|
||||||
|
*/
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
export interface NfcStatusChangedEvent {
|
||||||
|
/**
|
||||||
|
* Whether NFC was enabled or disabled.
|
||||||
|
*/
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
export interface StartScanSessionOptions {
|
||||||
|
/**
|
||||||
|
* If `true`, the scan session is stopped after the first tag is detected.
|
||||||
|
* Default: `false`
|
||||||
|
*/
|
||||||
|
once?: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, a scan feedback (sound) is played when a tag is detected.
|
||||||
|
* Only available on iOS.
|
||||||
|
* Default: `false`
|
||||||
|
*/
|
||||||
|
scanSoundEnabled?: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, the scan session is started with alert message.
|
||||||
|
* Only available on iOS.
|
||||||
|
* Default: `false`
|
||||||
|
*/
|
||||||
|
alertMessageEnabled?: boolean;
|
||||||
|
}
|
||||||
|
export interface TagDetectedEvent {
|
||||||
|
/**
|
||||||
|
* The ID (serial number) of the tag.
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* The tech types of the tag (e.g. 'ndef', 'mifare', etc.).
|
||||||
|
*/
|
||||||
|
techTypes: string[];
|
||||||
|
/**
|
||||||
|
* The NDEF messages contained in the tag.
|
||||||
|
*/
|
||||||
|
messages: NdefMessage[];
|
||||||
|
}
|
||||||
|
export interface NdefMessage {
|
||||||
|
/**
|
||||||
|
* The NDEF records contained in the message.
|
||||||
|
*/
|
||||||
|
records: NdefRecord[];
|
||||||
|
}
|
||||||
|
export interface NdefRecord {
|
||||||
|
/**
|
||||||
|
* The ID of the record. May be empty.
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* The TNF (Type Name Format) of the record.
|
||||||
|
* @see NfcTnf for possible values.
|
||||||
|
*/
|
||||||
|
tnf: number;
|
||||||
|
/**
|
||||||
|
* The type of the record (e.g. 'T' for text, 'U' for URI).
|
||||||
|
*/
|
||||||
|
type: string;
|
||||||
|
/**
|
||||||
|
* The payload of the record as string, if available.
|
||||||
|
*/
|
||||||
|
payload?: string;
|
||||||
|
/**
|
||||||
|
* The language code of the record (for text records).
|
||||||
|
* Only consistently available on Android.
|
||||||
|
*/
|
||||||
|
languageCode?: string;
|
||||||
|
/**
|
||||||
|
* The URI of the record (for URI records).
|
||||||
|
*/
|
||||||
|
uri?: string;
|
||||||
|
/**
|
||||||
|
* The text content of the record (for text records).
|
||||||
|
*/
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
export interface WriteOptions {
|
||||||
|
/**
|
||||||
|
* The NDEF message to write.
|
||||||
|
*/
|
||||||
|
message: NdefMessage;
|
||||||
|
}
|
||||||
|
export interface ShareOptions {
|
||||||
|
/**
|
||||||
|
* The NDEF message to share.
|
||||||
|
*/
|
||||||
|
message: NdefMessage;
|
||||||
|
}
|
||||||
|
export interface PluginListenerHandle {
|
||||||
|
/**
|
||||||
|
* Remove the listener.
|
||||||
|
*/
|
||||||
|
remove: () => Promise<void>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* NFC TNF (Type Name Format) Constants
|
||||||
|
* These determine how to interpret the type field.
|
||||||
|
*/
|
||||||
|
export declare enum NfcTnf {
|
||||||
|
EMPTY = 0,
|
||||||
|
WELL_KNOWN = 1,
|
||||||
|
MIME_MEDIA = 2,
|
||||||
|
ABSOLUTE_URI = 3,
|
||||||
|
EXTERNAL_TYPE = 4,
|
||||||
|
UNKNOWN = 5,
|
||||||
|
UNCHANGED = 6,
|
||||||
|
RESERVED = 7
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* NFC RTD (Record Type Definition) Constants
|
||||||
|
* These are standardized type names for common record types.
|
||||||
|
*/
|
||||||
|
export declare class NfcRtd {
|
||||||
|
static readonly TEXT = "T";
|
||||||
|
static readonly URI = "U";
|
||||||
|
static readonly SMART_POSTER = "Sp";
|
||||||
|
static readonly ALTERNATIVE_CARRIER = "ac";
|
||||||
|
static readonly HANDOVER_CARRIER = "Hc";
|
||||||
|
static readonly HANDOVER_REQUEST = "Hr";
|
||||||
|
static readonly HANDOVER_SELECT = "Hs";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Error codes that might be returned by NFC operations
|
||||||
|
*/
|
||||||
|
export declare enum NfcErrorType {
|
||||||
|
NOT_SUPPORTED = "not_supported",
|
||||||
|
NOT_ENABLED = "not_enabled",
|
||||||
|
PERMISSION_DENIED = "permission_denied",
|
||||||
|
NO_TAG = "no_tag",
|
||||||
|
TAG_ERROR = "tag_error",
|
||||||
|
IO_ERROR = "io_error",
|
||||||
|
TIMEOUT = "timeout",
|
||||||
|
CANCELLED = "cancelled",
|
||||||
|
UNEXPECTED_ERROR = "unexpected_error"
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Standard error structure for NFC operations
|
||||||
|
*/
|
||||||
|
export interface NfcError extends Error {
|
||||||
|
code: NfcErrorType;
|
||||||
|
message: string;
|
||||||
|
detail?: any;
|
||||||
|
}
|
||||||
|
export interface NFCDefinition {
|
||||||
|
id: string;
|
||||||
|
data: string;
|
||||||
|
}
|
43
simplepwa/universal-nfc/dist/definitions.js
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* NFC TNF (Type Name Format) Constants
|
||||||
|
* These determine how to interpret the type field.
|
||||||
|
*/
|
||||||
|
export var NfcTnf;
|
||||||
|
(function (NfcTnf) {
|
||||||
|
NfcTnf[NfcTnf["EMPTY"] = 0] = "EMPTY";
|
||||||
|
NfcTnf[NfcTnf["WELL_KNOWN"] = 1] = "WELL_KNOWN";
|
||||||
|
NfcTnf[NfcTnf["MIME_MEDIA"] = 2] = "MIME_MEDIA";
|
||||||
|
NfcTnf[NfcTnf["ABSOLUTE_URI"] = 3] = "ABSOLUTE_URI";
|
||||||
|
NfcTnf[NfcTnf["EXTERNAL_TYPE"] = 4] = "EXTERNAL_TYPE";
|
||||||
|
NfcTnf[NfcTnf["UNKNOWN"] = 5] = "UNKNOWN";
|
||||||
|
NfcTnf[NfcTnf["UNCHANGED"] = 6] = "UNCHANGED";
|
||||||
|
NfcTnf[NfcTnf["RESERVED"] = 7] = "RESERVED";
|
||||||
|
})(NfcTnf || (NfcTnf = {}));
|
||||||
|
/**
|
||||||
|
* NFC RTD (Record Type Definition) Constants
|
||||||
|
* These are standardized type names for common record types.
|
||||||
|
*/
|
||||||
|
export class NfcRtd {
|
||||||
|
}
|
||||||
|
NfcRtd.TEXT = "T";
|
||||||
|
NfcRtd.URI = "U";
|
||||||
|
NfcRtd.SMART_POSTER = "Sp";
|
||||||
|
NfcRtd.ALTERNATIVE_CARRIER = "ac";
|
||||||
|
NfcRtd.HANDOVER_CARRIER = "Hc";
|
||||||
|
NfcRtd.HANDOVER_REQUEST = "Hr";
|
||||||
|
NfcRtd.HANDOVER_SELECT = "Hs";
|
||||||
|
/**
|
||||||
|
* Error codes that might be returned by NFC operations
|
||||||
|
*/
|
||||||
|
export var NfcErrorType;
|
||||||
|
(function (NfcErrorType) {
|
||||||
|
NfcErrorType["NOT_SUPPORTED"] = "not_supported";
|
||||||
|
NfcErrorType["NOT_ENABLED"] = "not_enabled";
|
||||||
|
NfcErrorType["PERMISSION_DENIED"] = "permission_denied";
|
||||||
|
NfcErrorType["NO_TAG"] = "no_tag";
|
||||||
|
NfcErrorType["TAG_ERROR"] = "tag_error";
|
||||||
|
NfcErrorType["IO_ERROR"] = "io_error";
|
||||||
|
NfcErrorType["TIMEOUT"] = "timeout";
|
||||||
|
NfcErrorType["CANCELLED"] = "cancelled";
|
||||||
|
NfcErrorType["UNEXPECTED_ERROR"] = "unexpected_error";
|
||||||
|
})(NfcErrorType || (NfcErrorType = {}));
|
5
simplepwa/universal-nfc/dist/index.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./definitions.js";
|
||||||
|
export * from "./web.js";
|
||||||
|
export * from "./nfc.js";
|
||||||
|
export * from "./utils.js";
|
||||||
|
export * from "./simple-nfc.js";
|
5
simplepwa/universal-nfc/dist/index.js
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./definitions.js";
|
||||||
|
export * from "./web.js";
|
||||||
|
export * from "./nfc.js";
|
||||||
|
export * from "./utils.js";
|
||||||
|
export * from "./simple-nfc.js";
|
76
simplepwa/universal-nfc/dist/nfc.d.ts
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { IsEnabledResult, NfcStatusChangedEvent, StartScanSessionOptions, TagDetectedEvent, WriteOptions, ShareOptions, PluginListenerHandle } from "./definitions.js";
|
||||||
|
/**
|
||||||
|
* Main NFC class that provides access to NFC functionality.
|
||||||
|
* It automatically chooses the appropriate implementation for the current platform.
|
||||||
|
*/
|
||||||
|
export declare class Nfc {
|
||||||
|
private implementation;
|
||||||
|
private listeners;
|
||||||
|
constructor();
|
||||||
|
/**
|
||||||
|
* Internal method to monitor NFC status changes
|
||||||
|
*/
|
||||||
|
private monitorNfcStatus;
|
||||||
|
/**
|
||||||
|
* Check if NFC is enabled (Android) or available (iOS/Web).
|
||||||
|
* @returns Promise resolving to an object with an `enabled` boolean property
|
||||||
|
*/
|
||||||
|
isEnabled(): Promise<IsEnabledResult>;
|
||||||
|
/**
|
||||||
|
* Open NFC settings (Android) or app settings (iOS) or shows guidance (Web).
|
||||||
|
* This helps users enable NFC if it's disabled.
|
||||||
|
*/
|
||||||
|
openSettings(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Start scanning for NFC tags.
|
||||||
|
* @param options Configuration options for the scan session
|
||||||
|
*/
|
||||||
|
startScanSession(options?: StartScanSessionOptions): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Stop the current NFC scan session.
|
||||||
|
*/
|
||||||
|
stopScanSession(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Write an NDEF message to an NFC tag.
|
||||||
|
* @param options Object containing the NDEF message to write
|
||||||
|
*/
|
||||||
|
write(options: WriteOptions): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Make an NFC tag read-only.
|
||||||
|
* WARNING: This is a permanent operation that cannot be undone.
|
||||||
|
*/
|
||||||
|
makeReadOnly(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Format an NFC tag, erasing its contents and preparing it for writing.
|
||||||
|
*/
|
||||||
|
format(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Erase the contents of an NFC tag.
|
||||||
|
*/
|
||||||
|
erase(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Share NDEF data via NFC (Android only, not available on iOS or Web).
|
||||||
|
* @param options Object containing the NDEF message to share
|
||||||
|
*/
|
||||||
|
share(options: ShareOptions): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Stop sharing NDEF data via NFC (Android only).
|
||||||
|
*/
|
||||||
|
stopSharing(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Register an event listener.
|
||||||
|
* @param eventName Name of the event to listen for
|
||||||
|
* @param listenerFunc Callback function to invoke when the event occurs
|
||||||
|
* @returns A handle that can be used to remove the listener
|
||||||
|
*/
|
||||||
|
addListener(eventName: "nfcStatusChanged", listenerFunc: (status: NfcStatusChangedEvent) => void): Promise<PluginListenerHandle>;
|
||||||
|
addListener(eventName: "tagDetected", listenerFunc: (tag: TagDetectedEvent) => void): Promise<PluginListenerHandle>;
|
||||||
|
/**
|
||||||
|
* Remove all event listeners registered for this plugin.
|
||||||
|
*/
|
||||||
|
removeAllListeners(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Internal method to notify listeners of events
|
||||||
|
*/
|
||||||
|
private notifyListeners;
|
||||||
|
}
|
167
simplepwa/universal-nfc/dist/nfc.js
vendored
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
simplepwa/universal-nfc/dist/simple-nfc.d.ts
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { NFCDefinition } from "./definitions.js";
|
||||||
|
/**
|
||||||
|
* A simplified API for common NFC reading operations
|
||||||
|
*/
|
||||||
|
export declare class SimpleNfc {
|
||||||
|
private nfc;
|
||||||
|
private scanCallback;
|
||||||
|
constructor();
|
||||||
|
/**
|
||||||
|
* Check if NFC is available on this device/browser
|
||||||
|
*/
|
||||||
|
isAvailable(): Promise<boolean>;
|
||||||
|
/**
|
||||||
|
* Start scanning for NFC tags with a simplified callback
|
||||||
|
* The callback will receive text content and content type ('text', 'url', or 'other')
|
||||||
|
*/
|
||||||
|
startReading(callback: (content: string, type: string) => void): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Stop scanning for NFC tags
|
||||||
|
*/
|
||||||
|
stopReading(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Write a simple text to an NFC tag
|
||||||
|
*/
|
||||||
|
writeText(text: string): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Write a URL to an NFC tag
|
||||||
|
*/
|
||||||
|
writeUrl(url: string): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Read a simple NFC tag
|
||||||
|
*/
|
||||||
|
read(): NFCDefinition;
|
||||||
|
}
|
93
simplepwa/universal-nfc/dist/simple-nfc.js
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { Nfc } from "./nfc.js";
|
||||||
|
import { NfcUtils } from "./utils.js";
|
||||||
|
/**
|
||||||
|
* A simplified API for common NFC reading operations
|
||||||
|
*/
|
||||||
|
export class SimpleNfc {
|
||||||
|
constructor() {
|
||||||
|
this.scanCallback = null;
|
||||||
|
this.nfc = new Nfc();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check if NFC is available on this device/browser
|
||||||
|
*/
|
||||||
|
async isAvailable() {
|
||||||
|
try {
|
||||||
|
const result = await this.nfc.isEnabled();
|
||||||
|
return result.enabled;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Start scanning for NFC tags with a simplified callback
|
||||||
|
* The callback will receive text content and content type ('text', 'url', or 'other')
|
||||||
|
*/
|
||||||
|
async startReading(callback) {
|
||||||
|
this.scanCallback = callback;
|
||||||
|
// Set up the tag detection listener
|
||||||
|
await this.nfc.addListener("tagDetected", (tag) => {
|
||||||
|
var _a, _b, _c, _d;
|
||||||
|
// Try to get URL first
|
||||||
|
const url = NfcUtils.getUrlFromTag(tag);
|
||||||
|
if (url) {
|
||||||
|
(_a = this.scanCallback) === null || _a === void 0 ? void 0 : _a.call(this, url, "url");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Then try to get text
|
||||||
|
const text = NfcUtils.getTextFromTag(tag);
|
||||||
|
if (text) {
|
||||||
|
(_b = this.scanCallback) === null || _b === void 0 ? void 0 : _b.call(this, text, "text");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If we got here, we have other content
|
||||||
|
if (tag.messages.length > 0 && tag.messages[0].records.length > 0) {
|
||||||
|
const firstRecord = tag.messages[0].records[0];
|
||||||
|
(_c = this.scanCallback) === null || _c === void 0 ? void 0 : _c.call(this, firstRecord.payload || "Unknown content", "other");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(_d = this.scanCallback) === null || _d === void 0 ? void 0 : _d.call(this, "Empty tag", "other");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Start the actual scan
|
||||||
|
await this.nfc.startScanSession();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Stop scanning for NFC tags
|
||||||
|
*/
|
||||||
|
async stopReading() {
|
||||||
|
this.scanCallback = null;
|
||||||
|
await this.nfc.stopScanSession();
|
||||||
|
await this.nfc.removeAllListeners();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Write a simple text to an NFC tag
|
||||||
|
*/
|
||||||
|
async writeText(text) {
|
||||||
|
const textRecord = NfcUtils.createTextRecord(text);
|
||||||
|
await this.nfc.write({
|
||||||
|
message: {
|
||||||
|
records: [textRecord],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Write a URL to an NFC tag
|
||||||
|
*/
|
||||||
|
async writeUrl(url) {
|
||||||
|
const urlRecord = NfcUtils.createUriRecord(url);
|
||||||
|
await this.nfc.write({
|
||||||
|
message: {
|
||||||
|
records: [urlRecord],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Read a simple NFC tag
|
||||||
|
*/
|
||||||
|
read() {
|
||||||
|
// Dummy implementation
|
||||||
|
return { id: "2", data: "simple data" };
|
||||||
|
}
|
||||||
|
}
|
39
simplepwa/universal-nfc/dist/utils.d.ts
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { NdefRecord, NdefMessage } from "./definitions.js";
|
||||||
|
export declare class NfcUtils {
|
||||||
|
/**
|
||||||
|
* Creates a simple text record
|
||||||
|
*/
|
||||||
|
static createTextRecord(text: string, languageCode?: string): NdefRecord;
|
||||||
|
/**
|
||||||
|
* Creates a URI/URL record
|
||||||
|
*/
|
||||||
|
static createUriRecord(uri: string): NdefRecord;
|
||||||
|
/**
|
||||||
|
* Creates a complete NDEF message with one or more records
|
||||||
|
*/
|
||||||
|
static createMessage(records: NdefRecord[]): NdefMessage;
|
||||||
|
/**
|
||||||
|
* Extract text content from detected tag
|
||||||
|
* Returns the first text record found or null if none
|
||||||
|
*/
|
||||||
|
static getTextFromTag(tag: {
|
||||||
|
messages: NdefMessage[];
|
||||||
|
}): string | null;
|
||||||
|
/**
|
||||||
|
* Extract URL/URI from detected tag
|
||||||
|
* Returns the first URL record found or null if none
|
||||||
|
*/
|
||||||
|
static getUrlFromTag(tag: {
|
||||||
|
messages: NdefMessage[];
|
||||||
|
}): string | null;
|
||||||
|
/**
|
||||||
|
* Checks if this browser environment supports Web NFC
|
||||||
|
*/
|
||||||
|
static isWebNfcSupported(): boolean;
|
||||||
|
/**
|
||||||
|
* Checks if the device is likely to have NFC hardware
|
||||||
|
* (Not 100% reliable but useful as a hint)
|
||||||
|
*/
|
||||||
|
static isNfcLikelyAvailable(): boolean;
|
||||||
|
}
|
||||||
|
export declare function formatData(data: string): string;
|
95
simplepwa/universal-nfc/dist/utils.js
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// src/utils.ts
|
||||||
|
import { NfcTnf, NfcRtd } from "./definitions.js";
|
||||||
|
export class NfcUtils {
|
||||||
|
/**
|
||||||
|
* Creates a simple text record
|
||||||
|
*/
|
||||||
|
static createTextRecord(text, languageCode = "en") {
|
||||||
|
return {
|
||||||
|
id: "",
|
||||||
|
tnf: NfcTnf.WELL_KNOWN,
|
||||||
|
type: NfcRtd.TEXT,
|
||||||
|
payload: text,
|
||||||
|
languageCode,
|
||||||
|
text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Creates a URI/URL record
|
||||||
|
*/
|
||||||
|
static createUriRecord(uri) {
|
||||||
|
return {
|
||||||
|
id: "",
|
||||||
|
tnf: NfcTnf.WELL_KNOWN,
|
||||||
|
type: NfcRtd.URI,
|
||||||
|
payload: uri,
|
||||||
|
uri,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Creates a complete NDEF message with one or more records
|
||||||
|
*/
|
||||||
|
static createMessage(records) {
|
||||||
|
return { records };
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Extract text content from detected tag
|
||||||
|
* Returns the first text record found or null if none
|
||||||
|
*/
|
||||||
|
static getTextFromTag(tag) {
|
||||||
|
for (const message of tag.messages) {
|
||||||
|
for (const record of message.records) {
|
||||||
|
// First check the text field
|
||||||
|
if (record.text) {
|
||||||
|
return record.text;
|
||||||
|
}
|
||||||
|
// Then check for text record type
|
||||||
|
if ((record.type === "T" || record.type === "text") && record.payload) {
|
||||||
|
return record.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Extract URL/URI from detected tag
|
||||||
|
* Returns the first URL record found or null if none
|
||||||
|
*/
|
||||||
|
static getUrlFromTag(tag) {
|
||||||
|
for (const message of tag.messages) {
|
||||||
|
for (const record of message.records) {
|
||||||
|
// First check uri field
|
||||||
|
if (record.uri) {
|
||||||
|
return record.uri;
|
||||||
|
}
|
||||||
|
// Then check for URI record type
|
||||||
|
if ((record.type === "U" || record.type === "url") && record.payload) {
|
||||||
|
return record.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks if this browser environment supports Web NFC
|
||||||
|
*/
|
||||||
|
static isWebNfcSupported() {
|
||||||
|
return typeof window !== "undefined" && "NDEFReader" in window;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks if the device is likely to have NFC hardware
|
||||||
|
* (Not 100% reliable but useful as a hint)
|
||||||
|
*/
|
||||||
|
static isNfcLikelyAvailable() {
|
||||||
|
const ua = navigator.userAgent;
|
||||||
|
// Most Android devices have NFC these days
|
||||||
|
if (/android/i.test(ua))
|
||||||
|
return true;
|
||||||
|
// iOS detection is tricky as Web NFC isn't available anyway
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function formatData(data) {
|
||||||
|
// Implement your utility function here
|
||||||
|
return data.trim();
|
||||||
|
}
|
30
simplepwa/universal-nfc/dist/web.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { NfcPlugin, IsEnabledResult, StartScanSessionOptions, WriteOptions, ShareOptions, PluginListenerHandle } from "./definitions.js";
|
||||||
|
export declare class WebNfc implements NfcPlugin {
|
||||||
|
private scanSessionActive;
|
||||||
|
private scanOnce;
|
||||||
|
private listeners;
|
||||||
|
private ndefReader;
|
||||||
|
private nfcSupported;
|
||||||
|
constructor();
|
||||||
|
private detectNfcSupport;
|
||||||
|
isEnabled(): Promise<IsEnabledResult>;
|
||||||
|
openSettings(): Promise<void>;
|
||||||
|
startScanSession(options?: StartScanSessionOptions): Promise<void>;
|
||||||
|
private parseNdefReading;
|
||||||
|
private mapRecordTypeToTnf;
|
||||||
|
stopScanSession(): Promise<void>;
|
||||||
|
write(options: WriteOptions): Promise<void>;
|
||||||
|
makeReadOnly(): Promise<void>;
|
||||||
|
format(): Promise<void>;
|
||||||
|
erase(): Promise<void>;
|
||||||
|
share(options: ShareOptions): Promise<void>;
|
||||||
|
stopSharing(): Promise<void>;
|
||||||
|
addListener(eventName: "nfcStatusChanged" | "tagDetected", listenerFunc: (data: any) => void): Promise<PluginListenerHandle>;
|
||||||
|
private removeListener;
|
||||||
|
removeAllListeners(): Promise<void>;
|
||||||
|
private createCompatibilityError;
|
||||||
|
}
|
||||||
|
import { NFCDefinition } from "./definitions";
|
||||||
|
export declare class WebNFC {
|
||||||
|
read(): NFCDefinition;
|
||||||
|
}
|
311
simplepwa/universal-nfc/dist/web.js
vendored
Normal file
@ -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" };
|
||||||
|
}
|
||||||
|
}
|
46
simplepwa/universal-nfc/package.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "universal-nfc",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A framework-agnostic NFC package for reading/writing NFC tags in web apps and PWAs",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"prepare": "npm run build",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"nfc",
|
||||||
|
"ndef",
|
||||||
|
"rfid",
|
||||||
|
"web nfc",
|
||||||
|
"pwa",
|
||||||
|
"mobile"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/yourusername/universal-nfc.git"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"chrome >= 89"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"dist/",
|
||||||
|
"dist/index.js",
|
||||||
|
"dist/definitions.js",
|
||||||
|
"dist/web.js",
|
||||||
|
"dist/nfc.js",
|
||||||
|
"dist/simple-nfc.js.js",
|
||||||
|
"dist/utils.js.js",
|
||||||
|
"dist/definitions.js"
|
||||||
|
]
|
||||||
|
}
|