added ios support
This commit is contained in:
parent
0ff7c427e0
commit
37fc6f8b57
273
README.md
273
README.md
@ -1,6 +1,6 @@
|
|||||||
# Universal NFC
|
# 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.
|
A framework-agnostic NFC package for reading and writing NFC tags in web applications and Progressive Web Apps (PWAs), with iOS support options. 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://www.npmjs.com/package/universal-nfc)
|
||||||
[](https://github.com/yourusername/universal-nfc/blob/main/LICENSE)
|
[](https://github.com/yourusername/universal-nfc/blob/main/LICENSE)
|
||||||
@ -14,6 +14,7 @@ A framework-agnostic NFC package for reading and writing NFC tags in web applica
|
|||||||
- 📊 TypeScript support with comprehensive type definitions
|
- 📊 TypeScript support with comprehensive type definitions
|
||||||
- 🔄 Simple and advanced APIs for different use cases
|
- 🔄 Simple and advanced APIs for different use cases
|
||||||
- 🔌 Based on the Web NFC API standard
|
- 🔌 Based on the Web NFC API standard
|
||||||
|
- 🍎 iOS support via native bridge integration
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -29,10 +30,15 @@ yarn add universal-nfc
|
|||||||
|
|
||||||
## Platform Compatibility
|
## Platform Compatibility
|
||||||
|
|
||||||
| Feature | Chrome for Android (89+) | Chrome for Desktop | Safari iOS | Firefox | Edge |
|
| Feature | Chrome for Android (89+) | Chrome for Desktop | Safari iOS | Native iOS Apps | Native Android Apps | Firefox | Edge |
|
||||||
| ----------- | ------------------------ | ------------------ | ---------- | ------- | ---- |
|
|--------------|------------------------|------------------|-----------|----------------|----------------|---------|------|
|
||||||
| Reading NFC | ✅ | ❌ | ❌ | ❌ | ❌ |
|
| Reading NFC | ✅ | ❌ | ❌ | ✅* | ✅ | ❌ | ❌ |
|
||||||
| Writing NFC | ✅ | ❌ | ❌ | ❌ | ❌ |
|
| Writing NFC | ✅ | ❌ | ❌ | ✅* | ✅ | ❌ | ❌ |
|
||||||
|
|
||||||
|
\*iOS support requires a native app with an embedded WebView and bridge implementation.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Requirements for Web NFC:**
|
**Requirements for Web NFC:**
|
||||||
|
|
||||||
@ -41,6 +47,15 @@ yarn add universal-nfc
|
|||||||
- Device with NFC hardware
|
- Device with NFC hardware
|
||||||
- NFC enabled in device settings
|
- NFC enabled in device settings
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Requirements for iOS NFC:**
|
||||||
|
|
||||||
|
- iOS 11+ device with NFC hardware
|
||||||
|
- Native iOS app with NFC entitlements
|
||||||
|
- Core NFC framework integration
|
||||||
|
- Custom bridge implementation (see iOS Integration section)
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
### Reading NFC Tags (Simple API)
|
### Reading NFC Tags (Simple API)
|
||||||
@ -48,47 +63,45 @@ yarn add universal-nfc
|
|||||||
The simplest way to read NFC tags:
|
The simplest way to read NFC tags:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { SimpleNfc } from "universal-nfc";
|
import { SimpleNfc } from 'universal-nfc';
|
||||||
|
|
||||||
const nfcReader = new SimpleNfc();
|
const nfcReader = new SimpleNfc();
|
||||||
|
|
||||||
async function startReading() {
|
async function startReading() {
|
||||||
// First check if NFC is available
|
// First check if NFC is available
|
||||||
const available = await nfcReader.isAvailable();
|
const available = await nfcReader.isAvailable();
|
||||||
|
|
||||||
if (!available) {
|
if (!available) {
|
||||||
console.log("NFC is not available on this device/browser");
|
console.log('NFC is not available on this device/browser');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Start reading NFC tags with a callback
|
// Start reading NFC tags with a callback
|
||||||
await nfcReader.startReading((content, type) => {
|
await nfcReader.startReading((content, type) => {
|
||||||
console.log(`Read ${type} content:`, content);
|
console.log(`Read ${type} content:`, content);
|
||||||
|
|
||||||
// 'type' will be 'text', 'url', or 'other'
|
// 'type' will be 'text', 'url', or 'other'
|
||||||
if (type === "url") {
|
if (type === 'url') {
|
||||||
// Handle URL
|
// Handle URL
|
||||||
window.open(content, "_blank");
|
window.open(content, '_blank');
|
||||||
} else {
|
} else {
|
||||||
// Handle text or other content
|
// Handle text or other content
|
||||||
document.getElementById("result").textContent = content;
|
document.getElementById('result').textContent = content;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Scan started - tap an NFC tag");
|
console.log('Scan started - tap an NFC tag');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error starting NFC scan:", error);
|
console.error('Error starting NFC scan:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopReading() {
|
function stopReading() {
|
||||||
nfcReader.stopReading();
|
nfcReader.stopReading();
|
||||||
console.log("NFC reading stopped");
|
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
|
### Writing to NFC Tags
|
||||||
@ -117,6 +130,196 @@ async function writeUrlToTag() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## iOS Integration
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
iOS devices with NFC hardware (iPhone 7 and later running iOS 11+) support reading NFC tags, but with some important restrictions:
|
||||||
|
|
||||||
|
- **Safari browser cannot directly access NFC**: The Web NFC API is not supported in Safari.
|
||||||
|
- **Native app required**: NFC on iOS requires a native app built with the Core NFC framework.
|
||||||
|
- **Web apps can access NFC through a bridge**: Web content running in a WebView inside a native app can access NFC via a custom bridge.
|
||||||
|
|
||||||
|
### iOS Native Bridge Setup
|
||||||
|
|
||||||
|
To use Universal NFC with iOS, you need to:
|
||||||
|
|
||||||
|
1. Create a native iOS app with WebView.
|
||||||
|
2. Implement Core NFC.
|
||||||
|
3. Create a JavaScript bridge.
|
||||||
|
|
||||||
|
#### 1. Native iOS App with NFC Capabilities
|
||||||
|
|
||||||
|
First, ensure your app has NFC entitlements:
|
||||||
|
|
||||||
|
- Add the NFC entitlement to your app in Xcode.
|
||||||
|
- Add `NFCReaderUsageDescription` in `Info.plist`.
|
||||||
|
- Enable the "Near Field Communication Tag Reading" capability.
|
||||||
|
|
||||||
|
#### 2. Implement the NFC Bridge in Swift
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import WebKit
|
||||||
|
import CoreNFC
|
||||||
|
|
||||||
|
class NfcBridge: NSObject, NFCNDEFReaderSessionDelegate {
|
||||||
|
weak var webView: WKWebView?
|
||||||
|
var nfcSession: NFCNDEFReaderSession?
|
||||||
|
|
||||||
|
init(webView: WKWebView) {
|
||||||
|
self.webView = webView
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
// Register JavaScript interface
|
||||||
|
let bridgeScript = WKUserScript(
|
||||||
|
source: "window.nativeNfcBridge = {
|
||||||
|
isNfcEnabled: function() { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'isEnabled'}); },
|
||||||
|
startNfcScan: function(options) { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'startScan', options: options}); },
|
||||||
|
stopNfcScan: function() { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'stopScan'}); },
|
||||||
|
writeNfcTag: function(data) { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'writeTag', data: data}); },
|
||||||
|
openSettings: function() { return window.webkit.messageHandlers.nfcBridge.postMessage({action: 'openSettings'}); }
|
||||||
|
};",
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: false
|
||||||
|
)
|
||||||
|
|
||||||
|
let contentController = webView.configuration.userContentController
|
||||||
|
contentController.addUserScript(bridgeScript)
|
||||||
|
contentController.add(self, name: "nfcBridge")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle JavaScript messages
|
||||||
|
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||||
|
guard let body = message.body as? [String: Any],
|
||||||
|
let action = body["action"] as? String else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "isEnabled":
|
||||||
|
checkNfcAvailability()
|
||||||
|
case "startScan":
|
||||||
|
let options = body["options"] as? String ?? "{}"
|
||||||
|
startNfcSession(options: options)
|
||||||
|
case "stopScan":
|
||||||
|
stopNfcSession()
|
||||||
|
case "writeTag":
|
||||||
|
if let data = body["data"] as? String {
|
||||||
|
writeNfcTag(data: data)
|
||||||
|
}
|
||||||
|
case "openSettings":
|
||||||
|
openSettings()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func checkNfcAvailability() {
|
||||||
|
let isAvailable = NFCNDEFReaderSession.readingAvailable
|
||||||
|
sendToWebView(script: "window.dispatchEvent(new MessageEvent('message', {data: {type: 'nfcStatusChanged', enabled: \(isAvailable)}}));")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startNfcSession(options: String) {
|
||||||
|
guard NFCNDEFReaderSession.readingAvailable else {
|
||||||
|
sendToWebView(script: "console.error('NFC reading not available on this device');")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nfcSession = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
|
||||||
|
nfcSession?.alertMessage = "Hold your iPhone near an NFC tag"
|
||||||
|
nfcSession?.begin()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopNfcSession() {
|
||||||
|
nfcSession?.invalidate()
|
||||||
|
nfcSession = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func writeNfcTag(data: String) {
|
||||||
|
// Implement write logic
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openSettings() {
|
||||||
|
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendToWebView(script: String) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.webView?.evaluateJavaScript(script, completionHandler: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Initialize the Bridge in Your View Controller
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import UIKit
|
||||||
|
import WebKit
|
||||||
|
|
||||||
|
class WebViewController: UIViewController {
|
||||||
|
var webView: WKWebView!
|
||||||
|
var nfcBridge: NfcBridge!
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
webView = WKWebView(frame: view.bounds)
|
||||||
|
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
view.addSubview(webView)
|
||||||
|
|
||||||
|
nfcBridge = NfcBridge(webView: webView)
|
||||||
|
|
||||||
|
if let url = URL(string: "https://your-web-app-url.com") {
|
||||||
|
webView.load(URLRequest(url: url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the iOS Bridge in Your Web App
|
||||||
|
|
||||||
|
The native bridge will automatically be detected by Universal NFC when your web app runs inside the native iOS app's WebView:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { Nfc } from 'universal-nfc';
|
||||||
|
|
||||||
|
const nfc = new Nfc();
|
||||||
|
|
||||||
|
async function checkNfcSupport() {
|
||||||
|
const { enabled } = await nfc.isEnabled();
|
||||||
|
console.log('NFC supported and enabled:', enabled);
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
await nfc.startScanSession();
|
||||||
|
|
||||||
|
await nfc.addListener('tagDetected', (tag) => {
|
||||||
|
console.log('NFC tag detected via iOS bridge:', tag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting iOS-Specific Issues
|
||||||
|
|
||||||
|
#### "NFC reading is not supported in web browsers on iOS"
|
||||||
|
- This error occurs when trying to use NFC in Safari or a standard WebView.
|
||||||
|
- **Solution**: Use the native bridge approach with a custom iOS app.
|
||||||
|
|
||||||
|
#### "CoreNFC framework missing"
|
||||||
|
- Make sure you have the proper entitlements in your iOS app.
|
||||||
|
- Check that your iOS device supports NFC (iPhone 7 and later).
|
||||||
|
|
||||||
|
#### "Bridge communication error"
|
||||||
|
- Check the bridge implementation in your native app.
|
||||||
|
- Verify message format passed between WebView and native code.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## API Reference
|
## API Reference
|
||||||
|
|
||||||
### Core API (Nfc class)
|
### Core API (Nfc class)
|
||||||
@ -438,7 +641,3 @@ Permissions-Policy: nfc=self
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
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.
|
|
||||||
|
174
ios-bridge.ts
Normal file
174
ios-bridge.ts
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import { NfcPlugin, IsEnabledResult, TagDetectedEvent } from './definitions.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for native iOS app to implement for WebView communication
|
||||||
|
*/
|
||||||
|
interface NativeIosBridge {
|
||||||
|
isNfcEnabled?: () => Promise<boolean>;
|
||||||
|
startNfcScan?: (options: string) => Promise<void>;
|
||||||
|
stopNfcScan?: () => Promise<void>;
|
||||||
|
writeNfcTag?: (data: string) => Promise<void>;
|
||||||
|
openSettings?: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NFC implementation that communicates with a native iOS app via a bridge
|
||||||
|
* Note: This requires custom implementation in a native iOS app
|
||||||
|
*/
|
||||||
|
export class IosBridgeNfc implements NfcPlugin {
|
||||||
|
private bridge: NativeIosBridge;
|
||||||
|
private listeners: { [key: string]: Array<(...args: any[]) => void> } = {};
|
||||||
|
private isBridgeAvailable: boolean = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Look for the bridge in the global scope
|
||||||
|
// The native app needs to inject this object
|
||||||
|
this.bridge = (window as any).nativeNfcBridge || {};
|
||||||
|
this.isBridgeAvailable = !!(this.bridge.isNfcEnabled && this.bridge.startNfcScan);
|
||||||
|
|
||||||
|
// Set up message listener for tag detection events from native app
|
||||||
|
if (this.isBridgeAvailable) {
|
||||||
|
window.addEventListener('message', this.handleNativeMessage.bind(this));
|
||||||
|
console.log('iOS NFC bridge initialized');
|
||||||
|
} else {
|
||||||
|
console.warn('iOS NFC bridge not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleNativeMessage(event: MessageEvent) {
|
||||||
|
if (!event.data || typeof event.data !== 'object') return;
|
||||||
|
|
||||||
|
// Handle tag detected message from native app
|
||||||
|
if (event.data.type === 'nfcTagDetected' && event.data.tag) {
|
||||||
|
const tagListeners = this.listeners['tagDetected'] || [];
|
||||||
|
const tag = event.data.tag as TagDetectedEvent;
|
||||||
|
|
||||||
|
for (const listener of tagListeners) {
|
||||||
|
listener(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle NFC status change from native app
|
||||||
|
if (event.data.type === 'nfcStatusChanged' && event.data.enabled !== undefined) {
|
||||||
|
const statusListeners = this.listeners['nfcStatusChanged'] || [];
|
||||||
|
|
||||||
|
for (const listener of statusListeners) {
|
||||||
|
listener({ enabled: !!event.data.enabled });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async isEnabled(): Promise<IsEnabledResult> {
|
||||||
|
if (!this.isBridgeAvailable) {
|
||||||
|
return { enabled: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const enabled = await this.bridge.isNfcEnabled!();
|
||||||
|
return { enabled };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking NFC status through bridge:', error);
|
||||||
|
return { enabled: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async openSettings(): Promise<void> {
|
||||||
|
if (!this.isBridgeAvailable || !this.bridge.openSettings) {
|
||||||
|
throw new Error('NFC settings cannot be opened - bridge not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.bridge.openSettings();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error opening NFC settings through bridge:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async startScanSession(options?: any): Promise<void> {
|
||||||
|
if (!this.isBridgeAvailable) {
|
||||||
|
throw new Error('NFC scan cannot be started - bridge not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.bridge.startNfcScan!(JSON.stringify(options || {}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error starting NFC scan through bridge:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopScanSession(): Promise<void> {
|
||||||
|
if (!this.isBridgeAvailable || !this.bridge.stopNfcScan) {
|
||||||
|
return; // Just silently return if bridge not available
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.bridge.stopNfcScan!();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error stopping NFC scan through bridge:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async write(options: any): Promise<void> {
|
||||||
|
if (!this.isBridgeAvailable || !this.bridge.writeNfcTag) {
|
||||||
|
throw new Error('NFC write is not supported - bridge not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.bridge.writeNfcTag!(JSON.stringify(options));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error writing NFC tag through bridge:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async makeReadOnly(): Promise<void> {
|
||||||
|
throw new Error('makeReadOnly is not implemented in the iOS bridge');
|
||||||
|
}
|
||||||
|
|
||||||
|
async format(): Promise<void> {
|
||||||
|
throw new Error('format is not implemented in the iOS bridge');
|
||||||
|
}
|
||||||
|
|
||||||
|
async erase(): Promise<void> {
|
||||||
|
throw new Error('erase is not implemented in the iOS bridge');
|
||||||
|
}
|
||||||
|
|
||||||
|
async share(): Promise<void> {
|
||||||
|
throw new Error('share is not supported in iOS');
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopSharing(): Promise<void> {
|
||||||
|
throw new Error('share is not supported in iOS');
|
||||||
|
}
|
||||||
|
|
||||||
|
async addListener(
|
||||||
|
eventName: 'nfcStatusChanged' | 'tagDetected',
|
||||||
|
listenerFunc: (data: any) => void,
|
||||||
|
): Promise<any> {
|
||||||
|
if (!this.listeners[eventName]) {
|
||||||
|
this.listeners[eventName] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listeners[eventName].push(listenerFunc);
|
||||||
|
|
||||||
|
return {
|
||||||
|
remove: async () => {
|
||||||
|
this.removeListener(eventName, listenerFunc);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeListener(eventName: string, listenerFunc: (data: any) => void): void {
|
||||||
|
if (this.listeners[eventName]) {
|
||||||
|
this.listeners[eventName] = this.listeners[eventName].filter(
|
||||||
|
listener => listener !== listenerFunc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeAllListeners(): Promise<void> {
|
||||||
|
this.listeners = {};
|
||||||
|
}
|
||||||
|
}
|
73
ios-detection.ts
Normal file
73
ios-detection.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// src/ios-detection.ts
|
||||||
|
export interface IosSupportInfo {
|
||||||
|
isIos: boolean;
|
||||||
|
version: number | null;
|
||||||
|
supportsNfc: boolean;
|
||||||
|
requiresNative: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides detailed information about iOS NFC compatibility
|
||||||
|
*/
|
||||||
|
export class IosDetection {
|
||||||
|
/**
|
||||||
|
* Detects if the current device is running iOS and its NFC capabilities
|
||||||
|
*/
|
||||||
|
static getIosSupportInfo(): IosSupportInfo {
|
||||||
|
const userAgent = navigator.userAgent;
|
||||||
|
|
||||||
|
// Detect iOS
|
||||||
|
const isIos = /iphone|ipad|ipod/i.test(userAgent);
|
||||||
|
if (!isIos) {
|
||||||
|
return { isIos: false, version: null, supportsNfc: false, requiresNative: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract iOS version
|
||||||
|
const versionMatch = userAgent.match(/OS (\d+)_(\d+)_?(\d+)?/);
|
||||||
|
const version = versionMatch ?
|
||||||
|
parseInt(versionMatch[1], 10) + (parseInt(versionMatch[2], 10) / 10) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
// iOS NFC support info:
|
||||||
|
// - iOS 11+ supports NFC reading but requires a native app
|
||||||
|
// - No iOS version supports Web NFC API
|
||||||
|
const supportsNfc = version !== null && version >= 11;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isIos,
|
||||||
|
version,
|
||||||
|
supportsNfc,
|
||||||
|
requiresNative: true // iOS always requires native app for NFC
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this device supports NFC in any form (native or web)
|
||||||
|
*/
|
||||||
|
static hasNfcHardware(): boolean {
|
||||||
|
const iosInfo = this.getIosSupportInfo();
|
||||||
|
if (iosInfo.isIos) {
|
||||||
|
return iosInfo.supportsNfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Android/other platforms, we check based on the user agent
|
||||||
|
return /android/i.test(navigator.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides guidance for enabling NFC on iOS
|
||||||
|
*/
|
||||||
|
static getIosNfcGuidance(): string {
|
||||||
|
const iosInfo = this.getIosSupportInfo();
|
||||||
|
|
||||||
|
if (!iosInfo.isIos) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!iosInfo.supportsNfc) {
|
||||||
|
return 'Your iOS device does not support NFC or is running an iOS version below 11.0.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'NFC on iOS requires a native app. The web browser cannot directly access NFC hardware on iOS devices.';
|
||||||
|
}
|
||||||
|
}
|
15
nfc.ts
15
nfc.ts
@ -10,6 +10,8 @@ import {
|
|||||||
NfcErrorType,
|
NfcErrorType,
|
||||||
} from "./definitions.js";
|
} from "./definitions.js";
|
||||||
import { WebNfc } from "./web.js";
|
import { WebNfc } from "./web.js";
|
||||||
|
import { IosBridgeNfc } from './ios-bridge.js';
|
||||||
|
import { IosDetection } from './ios-detection.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main NFC class that provides access to NFC functionality.
|
* Main NFC class that provides access to NFC functionality.
|
||||||
@ -18,15 +20,24 @@ import { WebNfc } from "./web.js";
|
|||||||
export class Nfc {
|
export class Nfc {
|
||||||
private implementation: NfcPlugin;
|
private implementation: NfcPlugin;
|
||||||
private listeners: Map<string, Set<Function>> = new Map();
|
private listeners: Map<string, Set<Function>> = new Map();
|
||||||
|
private iosInfo: any;
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Currently we only have the Web implementation
|
this.iosInfo = IosDetection.getIosSupportInfo();
|
||||||
this.implementation = new WebNfc();
|
|
||||||
|
if (this.iosInfo.isIos && typeof (window as any).nativeNfcBridge !== 'undefined') {
|
||||||
|
console.log('Using iOS Native Bridge for NFC');
|
||||||
|
this.implementation = new IosBridgeNfc();
|
||||||
|
} else {
|
||||||
|
this.implementation = new WebNfc();
|
||||||
|
}
|
||||||
|
|
||||||
// Set up status monitoring to track NFC availability
|
// Set up status monitoring to track NFC availability
|
||||||
this.monitorNfcStatus();
|
this.monitorNfcStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal method to monitor NFC status changes
|
* Internal method to monitor NFC status changes
|
||||||
*/
|
*/
|
||||||
|
105
web.ts
105
web.ts
@ -6,8 +6,10 @@ import {
|
|||||||
ShareOptions,
|
ShareOptions,
|
||||||
PluginListenerHandle,
|
PluginListenerHandle,
|
||||||
TagDetectedEvent,
|
TagDetectedEvent,
|
||||||
|
NfcErrorType,
|
||||||
NdefRecord,
|
NdefRecord,
|
||||||
} from "./definitions.js";
|
} from './definitions.js';
|
||||||
|
import { IosDetection } from './ios-detection.js';
|
||||||
|
|
||||||
export class WebNfc implements NfcPlugin {
|
export class WebNfc implements NfcPlugin {
|
||||||
private scanSessionActive = false;
|
private scanSessionActive = false;
|
||||||
@ -15,73 +17,104 @@ export class WebNfc implements NfcPlugin {
|
|||||||
private listeners: { [key: string]: Array<(...args: any[]) => void> } = {};
|
private listeners: { [key: string]: Array<(...args: any[]) => void> } = {};
|
||||||
private ndefReader: any = null;
|
private ndefReader: any = null;
|
||||||
private nfcSupported: boolean = false;
|
private nfcSupported: boolean = false;
|
||||||
|
private iosInfo: any = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.detectNfcSupport();
|
this.detectNfcSupport();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private detectNfcSupport() {
|
private detectNfcSupport() {
|
||||||
// Check if Web NFC API is available
|
// Check for iOS first
|
||||||
if (typeof window !== "undefined") {
|
this.iosInfo = IosDetection.getIosSupportInfo();
|
||||||
// Primary check for NDEFReader
|
|
||||||
this.nfcSupported = "NDEFReader" in window;
|
if (this.iosInfo.isIos) {
|
||||||
|
this.nfcSupported = false;
|
||||||
|
console.info('iOS device detected:', this.iosInfo);
|
||||||
|
|
||||||
|
if (this.iosInfo.supportsNfc) {
|
||||||
|
console.warn('iOS NFC requires a native app wrapper - web browser access is not supported');
|
||||||
|
} else {
|
||||||
|
console.warn('NFC is not supported on this iOS device or iOS version');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Web NFC API for other platforms (primarily Android)
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
this.nfcSupported = 'NDEFReader' in window;
|
||||||
|
|
||||||
// Log platform info for debugging
|
|
||||||
const userAgent = navigator.userAgent;
|
const userAgent = navigator.userAgent;
|
||||||
const isHttps = window.location.protocol === "https:";
|
const isHttps = window.location.protocol === 'https:';
|
||||||
const isPWA = window.matchMedia("(display-mode: standalone)").matches;
|
const isPWA = window.matchMedia('(display-mode: standalone)').matches;
|
||||||
|
|
||||||
console.info("NFC Support Check:", {
|
console.info('NFC Support Check:', {
|
||||||
supported: this.nfcSupported,
|
supported: this.nfcSupported,
|
||||||
isHttps: isHttps,
|
isHttps: isHttps,
|
||||||
isPWA: isPWA,
|
isPWA: isPWA,
|
||||||
isAndroid: /android/i.test(userAgent),
|
isAndroid: /android/i.test(userAgent),
|
||||||
isIOS: /iphone|ipad|ipod/i.test(userAgent),
|
isChrome: /chrome/i.test(userAgent)
|
||||||
isChrome: /chrome/i.test(userAgent),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.nfcSupported) {
|
if (!this.nfcSupported) {
|
||||||
// Provide helpful message about browser compatibility
|
const reason = !isHttps ? 'NFC requires HTTPS' :
|
||||||
const reason = !isHttps
|
!/android/i.test(userAgent) ? 'NFC Web API only supported on Android' :
|
||||||
? "NFC requires HTTPS"
|
!/chrome/i.test(userAgent) ? 'NFC Web API only supported in Chrome-based browsers' :
|
||||||
: !/android/i.test(userAgent)
|
'This browser does not support the Web NFC API';
|
||||||
? "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}`);
|
console.warn(`Web NFC not available: ${reason}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.nfcSupported = false;
|
this.nfcSupported = false;
|
||||||
console.warn("Web NFC not available: Not in browser environment");
|
console.warn('Web NFC not available: Not in browser environment');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async isEnabled(): Promise<IsEnabledResult> {
|
async isEnabled(): Promise<IsEnabledResult> {
|
||||||
|
// For iOS, provide accurate info without throwing errors
|
||||||
|
if (this.iosInfo?.isIos) {
|
||||||
|
return { enabled: false };
|
||||||
|
}
|
||||||
|
|
||||||
return { enabled: this.nfcSupported };
|
return { enabled: this.nfcSupported };
|
||||||
}
|
}
|
||||||
|
|
||||||
async openSettings(): Promise<void> {
|
async openSettings(): Promise<void> {
|
||||||
|
// Special handling for iOS
|
||||||
|
if (this.iosInfo?.isIos) {
|
||||||
|
const message = IosDetection.getIosNfcGuidance();
|
||||||
|
|
||||||
|
// Use alert() on iOS to show guidance
|
||||||
|
if (typeof alert === 'function') {
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('iOS NFC guidance:', message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.nfcSupported) {
|
if (!this.nfcSupported) {
|
||||||
throw this.createCompatibilityError();
|
throw this.createCompatibilityError();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(
|
console.warn('openSettings: On web, users must enable NFC in device settings manually.');
|
||||||
"openSettings: On web, users must enable NFC in device settings manually."
|
|
||||||
);
|
|
||||||
|
|
||||||
// Provide instructions based on browser detection
|
// Provide instructions based on browser detection
|
||||||
if (/android/i.test(navigator.userAgent)) {
|
if (/android/i.test(navigator.userAgent)) {
|
||||||
alert(
|
alert('Please enable NFC in your device settings: Settings > Connected devices > Connection preferences > NFC');
|
||||||
"Please enable NFC in your device settings: Settings > Connected devices > Connection preferences > NFC"
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
alert("Please ensure NFC is enabled on your device.");
|
alert('Please ensure NFC is enabled on your device.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async startScanSession(options?: StartScanSessionOptions): Promise<void> {
|
async startScanSession(options?: StartScanSessionOptions): Promise<void> {
|
||||||
|
|
||||||
|
if (this.iosInfo?.isIos) {
|
||||||
|
throw new Error("NFC reading is not supported in web browsers on iOS. " +
|
||||||
|
"To use NFC on iOS, you need a native app implementation.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.nfcSupported) {
|
if (!this.nfcSupported) {
|
||||||
throw this.createCompatibilityError();
|
throw this.createCompatibilityError();
|
||||||
}
|
}
|
||||||
@ -364,18 +397,10 @@ export class WebNfc implements NfcPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createCompatibilityError(): Error {
|
private createCompatibilityError(): Error {
|
||||||
return new Error(
|
if (this.iosInfo?.isIos) {
|
||||||
"Web NFC API is not supported in this browser. NFC Web API requires Chrome 89+ on Android with NFC hardware, running over HTTPS."
|
return new Error("iOS browsers don't support the Web NFC API. To use NFC on iOS, you need a native app implementation.");
|
||||||
);
|
}
|
||||||
}
|
|
||||||
}
|
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.');
|
||||||
|
|
||||||
import { NFCDefinition } from "./definitions";
|
|
||||||
|
|
||||||
export class WebNFC {
|
|
||||||
// Implement your web NFC functionalities here
|
|
||||||
read(): NFCDefinition {
|
|
||||||
// Dummy implementation
|
|
||||||
return { id: "1", data: "sample data" };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user