unfc/nfc.ts
2025-03-04 21:32:53 +01:00

230 lines
6.0 KiB
TypeScript

import {
NfcPlugin,
IsEnabledResult,
NfcStatusChangedEvent,
StartScanSessionOptions,
TagDetectedEvent,
WriteOptions,
ShareOptions,
PluginListenerHandle,
NfcErrorType,
} from "./definitions.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.
* It automatically chooses the appropriate implementation for the current platform.
*/
export class Nfc {
private implementation: NfcPlugin;
private listeners: Map<string, Set<Function>> = new Map();
private iosInfo: any;
constructor() {
this.iosInfo = IosDetection.getIosSupportInfo();
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
this.monitorNfcStatus();
}
/**
* Internal method to monitor NFC status changes
*/
private async monitorNfcStatus(): Promise<void> {
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
*/
public async isEnabled(): Promise<IsEnabledResult> {
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.
*/
public async openSettings(): Promise<void> {
return this.implementation.openSettings();
}
/**
* Start scanning for NFC tags.
* @param options Configuration options for the scan session
*/
public async startScanSession(
options?: StartScanSessionOptions
): Promise<void> {
try {
return await this.implementation.startScanSession(options);
} catch (error: any) {
// Convert to a standardized error object if possible
const nfcError: any = new Error(
error.message || "Failed to start NFC scan"
);
if (
error.message?.includes("not supported") ||
error.name === "NotSupportedError"
) {
nfcError.code = NfcErrorType.NOT_SUPPORTED;
} else if (
error.message?.includes("permission") ||
error.name === "NotAllowedError"
) {
nfcError.code = NfcErrorType.PERMISSION_DENIED;
} else if (error.message?.includes("not enabled")) {
nfcError.code = NfcErrorType.NOT_ENABLED;
} else {
nfcError.code = NfcErrorType.UNEXPECTED_ERROR;
}
throw nfcError;
}
}
/**
* Stop the current NFC scan session.
*/
public async stopScanSession(): Promise<void> {
return this.implementation.stopScanSession();
}
/**
* Write an NDEF message to an NFC tag.
* @param options Object containing the NDEF message to write
*/
public async write(options: WriteOptions): Promise<void> {
return this.implementation.write(options);
}
/**
* Make an NFC tag read-only.
* WARNING: This is a permanent operation that cannot be undone.
*/
public async makeReadOnly(): Promise<void> {
return this.implementation.makeReadOnly();
}
/**
* Format an NFC tag, erasing its contents and preparing it for writing.
*/
public async format(): Promise<void> {
return this.implementation.format();
}
/**
* Erase the contents of an NFC tag.
*/
public async erase(): Promise<void> {
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
*/
public async share(options: ShareOptions): Promise<void> {
return this.implementation.share(options);
}
/**
* Stop sharing NDEF data via NFC (Android only).
*/
public async stopSharing(): Promise<void> {
return this.implementation.stopSharing();
}
/**
* 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
*/
public async addListener(
eventName: "nfcStatusChanged",
listenerFunc: (status: NfcStatusChangedEvent) => void
): Promise<PluginListenerHandle>;
public async addListener(
eventName: "tagDetected",
listenerFunc: (tag: TagDetectedEvent) => void
): Promise<PluginListenerHandle>;
public async addListener(
eventName: string,
listenerFunc: (data: any) => void
): Promise<PluginListenerHandle> {
// Add to our internal listener registry
if (!this.listeners.has(eventName)) {
this.listeners.set(eventName, new Set());
}
this.listeners.get(eventName)?.add(listenerFunc);
// Register with the implementation
const handle = await this.implementation.addListener(
eventName as "nfcStatusChanged" | "tagDetected" as any,
listenerFunc
);
// Return a handle that will clean up properly
return {
remove: async () => {
this.listeners.get(eventName)?.delete(listenerFunc);
return handle.remove();
},
};
}
/**
* Remove all event listeners registered for this plugin.
*/
public async removeAllListeners(): Promise<void> {
// Clear our internal listener registry
this.listeners.clear();
// Remove listeners from the implementation
return this.implementation.removeAllListeners();
}
/**
* Internal method to notify listeners of events
*/
private notifyListeners(eventName: string, data: any): void {
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);
}
}
}
}
}