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> = 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 { 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 { 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 { return this.implementation.openSettings(); } /** * Start scanning for NFC tags. * @param options Configuration options for the scan session */ public async startScanSession( options?: StartScanSessionOptions ): Promise { 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 { 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 { 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 { return this.implementation.makeReadOnly(); } /** * Format an NFC tag, erasing its contents and preparing it for writing. */ public async format(): Promise { return this.implementation.format(); } /** * Erase the contents of an NFC tag. */ public async erase(): Promise { 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 { return this.implementation.share(options); } /** * Stop sharing NDEF data via NFC (Android only). */ public async stopSharing(): Promise { 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; public async addListener( eventName: "tagDetected", listenerFunc: (tag: TagDetectedEvent) => void ): Promise; public async addListener( eventName: string, listenerFunc: (data: any) => void ): Promise { // 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 { // 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); } } } } }