UNPKG

capacitor-biometric-authentication

Version:

Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!

1,212 lines (1,197 loc) 45.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.BiometricErrorCode = void 0; (function (BiometricErrorCode) { BiometricErrorCode["BIOMETRIC_UNAVAILABLE"] = "BIOMETRIC_UNAVAILABLE"; BiometricErrorCode["AUTHENTICATION_FAILED"] = "AUTHENTICATION_FAILED"; BiometricErrorCode["USER_CANCELLED"] = "USER_CANCELLED"; BiometricErrorCode["TIMEOUT"] = "TIMEOUT"; BiometricErrorCode["LOCKOUT"] = "LOCKOUT"; BiometricErrorCode["NOT_ENROLLED"] = "NOT_ENROLLED"; BiometricErrorCode["PLATFORM_NOT_SUPPORTED"] = "PLATFORM_NOT_SUPPORTED"; BiometricErrorCode["UNKNOWN_ERROR"] = "UNKNOWN_ERROR"; })(exports.BiometricErrorCode || (exports.BiometricErrorCode = {})); var BiometryType; (function (BiometryType) { BiometryType["FINGERPRINT"] = "fingerprint"; BiometryType["FACE_ID"] = "faceId"; BiometryType["TOUCH_ID"] = "touchId"; BiometryType["IRIS"] = "iris"; BiometryType["MULTIPLE"] = "multiple"; BiometryType["UNKNOWN"] = "unknown"; })(BiometryType || (BiometryType = {})); class PlatformDetector { constructor() { this.platformInfo = null; } static getInstance() { if (!PlatformDetector.instance) { PlatformDetector.instance = new PlatformDetector(); } return PlatformDetector.instance; } detect() { var _a, _b; if (this.platformInfo) { return this.platformInfo; } const info = { name: 'unknown', isCapacitor: false, isReactNative: false, isCordova: false, isWeb: false, isIOS: false, isAndroid: false, isElectron: false }; // Check if we're in a browser environment if (typeof window !== 'undefined' && typeof document !== 'undefined') { info.isWeb = true; // Check for Capacitor if (window.Capacitor) { info.isCapacitor = true; const capacitor = window.Capacitor; const platform = (_a = capacitor === null || capacitor === void 0 ? void 0 : capacitor.getPlatform) === null || _a === void 0 ? void 0 : _a.call(capacitor); if (platform) { info.name = platform; info.isIOS = platform === 'ios'; info.isAndroid = platform === 'android'; info.isWeb = platform === 'web'; } } // Check for Cordova else if (window.cordova || window.phonegap) { info.isCordova = true; info.name = 'cordova'; const userAgent = navigator.userAgent || navigator.vendor || window.opera || ''; if (/android/i.test(userAgent)) { info.isAndroid = true; info.name = 'android'; } else if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { info.isIOS = true; info.name = 'ios'; } } // Check for Electron else if (((_b = window.process) === null || _b === void 0 ? void 0 : _b.type) === 'renderer' || navigator.userAgent.indexOf('Electron') !== -1) { info.isElectron = true; info.name = 'electron'; } // Default to web else { info.name = 'web'; } } // Check for React Native else if (typeof global !== 'undefined' && global.nativePerformanceNow) { info.isReactNative = true; info.name = 'react-native'; // Try to detect platform in React Native try { // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any const { Platform } = require('react-native'); if (Platform) { info.name = Platform.OS; info.isIOS = Platform.OS === 'ios'; info.isAndroid = Platform.OS === 'android'; } } catch (_c) { // React Native not available } } // Node.js environment else if (typeof process !== 'undefined' && process.versions && process.versions.node) { info.name = 'node'; } // Get version info if available if (info.isCapacitor && typeof window !== 'undefined') { const capacitor = window.Capacitor; if (capacitor === null || capacitor === void 0 ? void 0 : capacitor.version) { info.version = capacitor.version; } } else if (info.isReactNative) { try { // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any const { Platform } = require('react-native'); info.version = Platform.Version; } catch (_d) { // Ignore } } this.platformInfo = info; return info; } isSupported() { const info = this.detect(); return info.isWeb || info.isCapacitor || info.isReactNative || info.isCordova; } getPlatformName() { return this.detect().name; } isNativePlatform() { const info = this.detect(); return (info.isIOS || info.isAndroid) && (info.isCapacitor || info.isReactNative || info.isCordova); } } class BiometricAuthCore { constructor() { this.config = { adapter: 'auto', debug: false, sessionDuration: 300000, // 5 minutes }; this.adapters = new Map(); this.currentAdapter = null; this.state = { isAuthenticated: false }; this.platformDetector = PlatformDetector.getInstance(); this.subscribers = new Set(); this.initialize(); } static getInstance() { if (!BiometricAuthCore.instance) { BiometricAuthCore.instance = new BiometricAuthCore(); } return BiometricAuthCore.instance; } async initialize() { // Detect platform and load appropriate adapter const platformInfo = this.platformDetector.detect(); if (this.config.debug) { console.warn('[BiometricAuth] Platform detected:', platformInfo); } // Load adapter based on platform await this.loadAdapter(platformInfo.name); } async loadAdapter(platform) { var _a; try { // Try custom adapters first if ((_a = this.config.customAdapters) === null || _a === void 0 ? void 0 : _a[platform]) { this.currentAdapter = this.config.customAdapters[platform]; return; } // Try to load from registered adapters if (this.adapters.has(platform)) { this.currentAdapter = this.adapters.get(platform); return; } // Dynamic import based on platform switch (platform) { case 'web': const { WebAdapter } = await Promise.resolve().then(function () { return WebAdapter$1; }); this.currentAdapter = new WebAdapter(); break; case 'ios': case 'android': // Check if Capacitor is available if (this.platformDetector.detect().isCapacitor) { const { CapacitorAdapter } = await Promise.resolve().then(function () { return CapacitorAdapter$1; }); this.currentAdapter = new CapacitorAdapter(); } else if (this.platformDetector.detect().isReactNative) { // Dynamic import for React Native try { const { ReactNativeAdapter } = await Promise.resolve().then(function () { return ReactNativeAdapter$1; }); this.currentAdapter = new ReactNativeAdapter(); } catch (_b) { throw new Error('React Native biometric module not installed. Please install react-native-biometrics'); } } break; case 'electron': const { ElectronAdapter } = await Promise.resolve().then(function () { return ElectronAdapter$1; }); this.currentAdapter = new ElectronAdapter(); break; default: throw new Error(`Platform ${platform} not supported`); } } catch (error) { if (this.config.debug) { console.warn('[BiometricAuth] Failed to load adapter:', error); } // Fallback to web adapter if available if (platform !== 'web' && this.platformDetector.detect().isWeb) { const { WebAdapter } = await Promise.resolve().then(function () { return WebAdapter$1; }); this.currentAdapter = new WebAdapter(); } } } configure(config) { this.config = Object.assign(Object.assign({}, this.config), config); // Re-initialize if adapter changed if (config.adapter && config.adapter !== 'auto') { this.loadAdapter(config.adapter); } } registerAdapter(name, adapter) { this.adapters.set(name, adapter); } async isAvailable() { if (!this.currentAdapter) { return false; } try { return await this.currentAdapter.isAvailable(); } catch (error) { if (this.config.debug) { console.warn('[BiometricAuth] isAvailable error:', error); } return false; } } async getSupportedBiometrics() { if (!this.currentAdapter) { return []; } try { return await this.currentAdapter.getSupportedBiometrics(); } catch (error) { if (this.config.debug) { console.warn('[BiometricAuth] getSupportedBiometrics error:', error); } return []; } } async authenticate(options) { if (!this.currentAdapter) { return { success: false, error: { code: exports.BiometricErrorCode.PLATFORM_NOT_SUPPORTED, message: 'No biometric adapter available for this platform' } }; } try { const result = await this.currentAdapter.authenticate(options); if (result.success) { this.updateState({ isAuthenticated: true, sessionId: result.sessionId, lastAuthTime: Date.now(), biometryType: result.biometryType, error: undefined }); // Set up session timeout if (this.config.sessionDuration && this.config.sessionDuration > 0) { setTimeout(() => { this.logout(); }, this.config.sessionDuration); } } else { this.updateState({ isAuthenticated: false, error: result.error }); } return result; } catch (error) { const errorResult = { success: false, error: { code: exports.BiometricErrorCode.UNKNOWN_ERROR, message: error instanceof Error ? error.message : 'Unknown error occurred', details: error } }; this.updateState({ isAuthenticated: false, error: errorResult.error }); return errorResult; } } async deleteCredentials() { if (!this.currentAdapter) { throw new Error('No biometric adapter available'); } await this.currentAdapter.deleteCredentials(); this.logout(); } async hasCredentials() { if (!this.currentAdapter) { return false; } try { return await this.currentAdapter.hasCredentials(); } catch (error) { if (this.config.debug) { console.warn('[BiometricAuth] hasCredentials error:', error); } return false; } } logout() { this.updateState({ isAuthenticated: false, sessionId: undefined, lastAuthTime: undefined, biometryType: undefined, error: undefined }); } getState() { return Object.assign({}, this.state); } isAuthenticated() { if (!this.state.isAuthenticated || !this.state.lastAuthTime) { return false; } // Check if session is still valid if (this.config.sessionDuration && this.config.sessionDuration > 0) { const elapsed = Date.now() - this.state.lastAuthTime; if (this.config.sessionDuration && elapsed > this.config.sessionDuration) { this.logout(); return false; } } return true; } subscribe(callback) { this.subscribers.add(callback); // Return unsubscribe function return () => { this.subscribers.delete(callback); }; } updateState(newState) { this.state = Object.assign(Object.assign({}, this.state), newState); // Notify subscribers this.subscribers.forEach(callback => { callback(this.getState()); }); } // Utility methods for common use cases async requireAuthentication(callback, options) { var _a; if (!this.isAuthenticated()) { const result = await this.authenticate(options); if (!result.success) { throw new Error(((_a = result.error) === null || _a === void 0 ? void 0 : _a.message) || 'Authentication failed'); } } return callback(); } async withAuthentication(callback, options) { var _a; if (!this.isAuthenticated()) { const result = await this.authenticate(options); if (!result.success) { throw new Error(((_a = result.error) === null || _a === void 0 ? void 0 : _a.message) || 'Authentication failed'); } } return callback(); } } class WebAdapter { constructor() { this.platform = 'web'; this.credentials = new Map(); // Set default Relying Party info this.rpId = window.location.hostname; this.rpName = document.title || 'Biometric Authentication'; } async isAvailable() { // Check if WebAuthn is supported if (!window.PublicKeyCredential) { return false; } // Check if platform authenticator is available try { const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); return available; } catch (_a) { return false; } } async getSupportedBiometrics() { if (!(await this.isAvailable())) { return []; } // WebAuthn doesn't provide specific biometry types // Return generic "multiple" as modern devices support various methods return [BiometryType.MULTIPLE]; } async authenticate(options) { var _a; try { // Check if WebAuthn is available if (!(await this.isAvailable())) { return { success: false, error: { code: exports.BiometricErrorCode.BIOMETRIC_UNAVAILABLE, message: 'WebAuthn is not available on this device' } }; } const webOptions = ((_a = options === null || options === void 0 ? void 0 : options.platform) === null || _a === void 0 ? void 0 : _a.web) || {}; // Try to get existing credential first const existingCredential = await this.getExistingCredential(webOptions); if (existingCredential) { return { success: true, biometryType: BiometryType.MULTIPLE, sessionId: this.generateSessionId(), platform: 'web' }; } // If no existing credential, create a new one const credential = await this.createCredential((options === null || options === void 0 ? void 0 : options.reason) || 'Authentication required', webOptions); if (credential) { // Store credential for future use const credentialId = this.arrayBufferToBase64(credential.rawId); this.credentials.set(credentialId, credential); this.saveCredentialId(credentialId); return { success: true, biometryType: BiometryType.MULTIPLE, sessionId: this.generateSessionId(), platform: 'web' }; } return { success: false, error: { code: exports.BiometricErrorCode.AUTHENTICATION_FAILED, message: 'Failed to authenticate' } }; } catch (error) { return this.handleError(error); } } async deleteCredentials() { this.credentials.clear(); localStorage.removeItem('biometric_credential_ids'); } async hasCredentials() { const storedIds = this.getStoredCredentialIds(); return storedIds.length > 0; } async getExistingCredential(options) { const storedIds = this.getStoredCredentialIds(); if (storedIds.length === 0) { return null; } try { const challenge = options.challenge || crypto.getRandomValues(new Uint8Array(32)); const publicKeyOptions = { challenge, rpId: options.rpId || this.rpId, timeout: options.timeout || 60000, userVerification: options.userVerification || 'preferred', allowCredentials: storedIds.map(id => ({ id: this.base64ToArrayBuffer(id), type: 'public-key' })) }; const credential = await navigator.credentials.get({ publicKey: publicKeyOptions }); return credential; } catch (_a) { return null; } } async createCredential(_reason, options) { try { const challenge = options.challenge || crypto.getRandomValues(new Uint8Array(32)); const userId = crypto.getRandomValues(new Uint8Array(32)); const publicKeyOptions = { challenge, rp: { id: options.rpId || this.rpId, name: options.rpName || this.rpName }, user: { id: userId, name: 'user@' + this.rpId, displayName: 'User' }, pubKeyCredParams: [ { type: 'public-key', alg: -7 }, // ES256 { type: 'public-key', alg: -257 } // RS256 ], authenticatorSelection: options.authenticatorSelection || { authenticatorAttachment: 'platform', userVerification: 'preferred', requireResidentKey: false, residentKey: 'discouraged' }, timeout: options.timeout || 60000, attestation: options.attestation || 'none' }; const credential = await navigator.credentials.create({ publicKey: publicKeyOptions }); return credential; } catch (_a) { return null; } } handleError(error) { let code = exports.BiometricErrorCode.UNKNOWN_ERROR; let message = 'An unknown error occurred'; if (error instanceof DOMException) { switch (error.name) { case 'NotAllowedError': code = exports.BiometricErrorCode.USER_CANCELLED; message = 'User cancelled the authentication'; break; case 'AbortError': code = exports.BiometricErrorCode.USER_CANCELLED; message = 'Authentication was aborted'; break; case 'SecurityError': code = exports.BiometricErrorCode.AUTHENTICATION_FAILED; message = 'Security error during authentication'; break; case 'InvalidStateError': code = exports.BiometricErrorCode.AUTHENTICATION_FAILED; message = 'Invalid state for authentication'; break; case 'NotSupportedError': code = exports.BiometricErrorCode.BIOMETRIC_UNAVAILABLE; message = 'WebAuthn is not supported'; break; default: message = error.message || message; } } else if (error instanceof Error) { message = error.message; } return { success: false, error: { code, message, details: error } }; } generateSessionId() { return Array.from(crypto.getRandomValues(new Uint8Array(16))) .map(b => b.toString(16).padStart(2, '0')) .join(''); } arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } base64ToArrayBuffer(base64) { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes.buffer; } getStoredCredentialIds() { const stored = localStorage.getItem('biometric_credential_ids'); if (!stored) { return []; } try { return JSON.parse(stored); } catch (_a) { return []; } } saveCredentialId(id) { const existing = this.getStoredCredentialIds(); if (!existing.includes(id)) { existing.push(id); localStorage.setItem('biometric_credential_ids', JSON.stringify(existing)); } } } var WebAdapter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, WebAdapter: WebAdapter }); class CapacitorAdapter { constructor() { this.platform = 'capacitor'; // Plugin will be loaded dynamically } async getPlugin() { if (this.capacitorPlugin) { return this.capacitorPlugin; } try { // Try to get the registered Capacitor plugin const capacitorCore = await import('@capacitor/core'); // Try using registerPlugin if available if (capacitorCore.registerPlugin) { try { this.capacitorPlugin = capacitorCore.registerPlugin('BiometricAuth'); if (this.capacitorPlugin) { return this.capacitorPlugin; } } catch (_a) { // Continue to fallback } } // Legacy support for older Capacitor versions const legacyPlugins = capacitorCore.Plugins; if (legacyPlugins === null || legacyPlugins === void 0 ? void 0 : legacyPlugins.BiometricAuth) { this.capacitorPlugin = legacyPlugins.BiometricAuth; return this.capacitorPlugin; } // If not found in Plugins, try direct import // This allows the plugin to work even if not properly registered const BiometricAuthPlugin = window.BiometricAuthPlugin; if (BiometricAuthPlugin) { this.capacitorPlugin = BiometricAuthPlugin; return this.capacitorPlugin; } throw new Error('BiometricAuth Capacitor plugin not found'); } catch (error) { throw new Error('Failed to load Capacitor plugin: ' + error.message); } } async isAvailable() { try { const plugin = await this.getPlugin(); const result = await plugin.isAvailable(); return result.isAvailable || false; } catch (_a) { return false; } } async getSupportedBiometrics() { try { const plugin = await this.getPlugin(); const result = await plugin.getSupportedBiometrics(); // Map Capacitor biometry types to our types return (result.biometryTypes || []).map((type) => { switch (type.toLowerCase()) { case 'fingerprint': return BiometryType.FINGERPRINT; case 'faceid': case 'face_id': return BiometryType.FACE_ID; case 'touchid': case 'touch_id': return BiometryType.TOUCH_ID; case 'iris': return BiometryType.IRIS; default: return BiometryType.UNKNOWN; } }).filter((type) => type !== BiometryType.UNKNOWN); } catch (_a) { return []; } } async authenticate(options) { var _a, _b; try { const plugin = await this.getPlugin(); // Map our options to Capacitor plugin options const capacitorOptions = Object.assign(Object.assign({ reason: (options === null || options === void 0 ? void 0 : options.reason) || 'Authenticate to continue', cancelTitle: options === null || options === void 0 ? void 0 : options.cancelTitle, fallbackTitle: options === null || options === void 0 ? void 0 : options.fallbackTitle, disableDeviceCredential: options === null || options === void 0 ? void 0 : options.disableDeviceCredential, maxAttempts: options === null || options === void 0 ? void 0 : options.maxAttempts, requireConfirmation: options === null || options === void 0 ? void 0 : options.requireConfirmation }, (((_a = options === null || options === void 0 ? void 0 : options.platform) === null || _a === void 0 ? void 0 : _a.android) || {})), (((_b = options === null || options === void 0 ? void 0 : options.platform) === null || _b === void 0 ? void 0 : _b.ios) || {})); const result = await plugin.authenticate(capacitorOptions); if (result.success) { const biometryType = this.mapBiometryType(result.biometryType); return { success: true, biometryType, sessionId: this.generateSessionId(), platform: 'capacitor' }; } else { return { success: false, error: this.mapError(result.error) }; } } catch (error) { return { success: false, error: this.mapError(error) }; } } async deleteCredentials() { try { const plugin = await this.getPlugin(); await plugin.deleteCredentials(); } catch (_a) { // Ignore errors when deleting credentials } } async hasCredentials() { try { const plugin = await this.getPlugin(); // Check if the plugin has a hasCredentials method if (typeof plugin.hasCredentials === 'function') { const result = await plugin.hasCredentials(); return result.hasCredentials || false; } // Fallback: assume credentials exist if biometrics are available return await this.isAvailable(); } catch (_a) { return false; } } mapBiometryType(type) { if (!type) { return BiometryType.UNKNOWN; } switch (type.toLowerCase()) { case 'fingerprint': return BiometryType.FINGERPRINT; case 'faceid': case 'face_id': return BiometryType.FACE_ID; case 'touchid': case 'touch_id': return BiometryType.TOUCH_ID; case 'iris': return BiometryType.IRIS; default: return BiometryType.UNKNOWN; } } mapError(error) { let code = exports.BiometricErrorCode.UNKNOWN_ERROR; let message = 'An unknown error occurred'; const errorObj = error; if (errorObj === null || errorObj === void 0 ? void 0 : errorObj.code) { switch (errorObj.code) { case 'BIOMETRIC_UNAVAILABLE': case 'UNAVAILABLE': code = exports.BiometricErrorCode.BIOMETRIC_UNAVAILABLE; message = errorObj.message || 'Biometric authentication is not available'; break; case 'USER_CANCELLED': case 'CANCELLED': case 'USER_CANCEL': code = exports.BiometricErrorCode.USER_CANCELLED; message = errorObj.message || 'User cancelled authentication'; break; case 'AUTHENTICATION_FAILED': case 'FAILED': code = exports.BiometricErrorCode.AUTHENTICATION_FAILED; message = errorObj.message || 'Authentication failed'; break; case 'TIMEOUT': code = exports.BiometricErrorCode.TIMEOUT; message = errorObj.message || 'Authentication timed out'; break; case 'LOCKOUT': code = exports.BiometricErrorCode.LOCKOUT; message = errorObj.message || 'Too many failed attempts'; break; case 'NOT_ENROLLED': code = exports.BiometricErrorCode.NOT_ENROLLED; message = errorObj.message || 'No biometric credentials enrolled'; break; default: message = errorObj.message || message; } } else if (error instanceof Error) { message = error.message; } else if (typeof error === 'string') { message = error; } return { code, message, details: error }; } generateSessionId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } } var CapacitorAdapter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, CapacitorAdapter: CapacitorAdapter }); var _a; // Create singleton instance const biometricAuth = BiometricAuthCore.getInstance(); // Export the main API (provider-less, like Zustand) const BiometricAuth = { // Core methods configure: (config) => biometricAuth.configure(config), isAvailable: () => biometricAuth.isAvailable(), getSupportedBiometrics: () => biometricAuth.getSupportedBiometrics(), authenticate: (options) => biometricAuth.authenticate(options), deleteCredentials: () => biometricAuth.deleteCredentials(), hasCredentials: () => biometricAuth.hasCredentials(), // State management logout: () => biometricAuth.logout(), getState: () => biometricAuth.getState(), isAuthenticated: () => biometricAuth.isAuthenticated(), subscribe: (callback) => biometricAuth.subscribe(callback), // Utility methods requireAuthentication: (callback, options) => biometricAuth.requireAuthentication(callback, options), withAuthentication: (callback, options) => biometricAuth.withAuthentication(callback, options), // Advanced usage registerAdapter: (name, adapter) => biometricAuth.registerAdapter(name, adapter), }; // For backward compatibility with Capacitor plugin registration if (typeof window !== 'undefined') { const capacitorGlobal = window; if ((_a = capacitorGlobal.Capacitor) === null || _a === void 0 ? void 0 : _a.registerPlugin) { // Register as a Capacitor plugin for backward compatibility const { registerPlugin } = capacitorGlobal.Capacitor; if (registerPlugin) { try { // Create a Capacitor-compatible plugin interface const BiometricAuthPlugin = { isAvailable: async () => ({ isAvailable: await BiometricAuth.isAvailable() }), getSupportedBiometrics: async () => ({ biometryTypes: await BiometricAuth.getSupportedBiometrics() }), authenticate: async (options) => { const result = await BiometricAuth.authenticate(options); return { success: result.success, error: result.error, biometryType: result.biometryType }; }, deleteCredentials: async () => { await BiometricAuth.deleteCredentials(); return {}; } }; // Register the plugin registerPlugin('BiometricAuth', { web: BiometricAuthPlugin }); } catch (_b) { // Ignore registration errors - not critical } } } } class ReactNativeAdapter { constructor() { this.platform = 'react-native'; // Biometrics module will be loaded dynamically } async getBiometrics() { if (this.biometrics) { return this.biometrics; } try { // Dynamic import for React Native biometrics // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any const ReactNativeBiometrics = require('react-native-biometrics').default; this.biometrics = new ReactNativeBiometrics(); return this.biometrics; } catch (_a) { throw new Error('React Native Biometrics not installed. Please run: npm install react-native-biometrics'); } } async isAvailable() { try { const biometrics = await this.getBiometrics(); const { available } = await biometrics.isSensorAvailable(); return available; } catch (_a) { return false; } } async getSupportedBiometrics() { try { const biometrics = await this.getBiometrics(); const { available, biometryType } = await biometrics.isSensorAvailable(); if (!available) { return []; } // Map React Native biometry types to our types switch (biometryType) { case 'TouchID': return [BiometryType.TOUCH_ID]; case 'FaceID': return [BiometryType.FACE_ID]; case 'Biometrics': case 'Fingerprint': return [BiometryType.FINGERPRINT]; default: return [BiometryType.UNKNOWN]; } } catch (_a) { return []; } } async authenticate(options) { try { const biometrics = await this.getBiometrics(); // Check if biometrics are available const { available, biometryType, error } = await biometrics.isSensorAvailable(); if (!available) { return { success: false, error: { code: exports.BiometricErrorCode.BIOMETRIC_UNAVAILABLE, message: error || 'Biometric authentication is not available' } }; } // Create signature for authentication const { success, signature, error: authError } = await biometrics.createSignature({ promptMessage: (options === null || options === void 0 ? void 0 : options.reason) || 'Authenticate', cancelButtonText: (options === null || options === void 0 ? void 0 : options.cancelTitle) || 'Cancel', payload: 'biometric-auth-payload' }); if (success && signature) { return { success: true, biometryType: this.mapBiometryType(biometryType), sessionId: this.generateSessionId(), platform: 'react-native' }; } else { return { success: false, error: this.mapError(authError) }; } } catch (error) { return { success: false, error: this.mapError(error) }; } } async deleteCredentials() { try { const biometrics = await this.getBiometrics(); await biometrics.deleteKeys(); } catch (_a) { // Ignore errors when deleting credentials } } async hasCredentials() { try { const biometrics = await this.getBiometrics(); const { keysExist } = await biometrics.biometricKeysExist(); return keysExist; } catch (_a) { return false; } } mapBiometryType(type) { if (!type) { return BiometryType.UNKNOWN; } switch (type) { case 'TouchID': return BiometryType.TOUCH_ID; case 'FaceID': return BiometryType.FACE_ID; case 'Biometrics': case 'Fingerprint': return BiometryType.FINGERPRINT; default: return BiometryType.UNKNOWN; } } mapError(error) { let code = exports.BiometricErrorCode.UNKNOWN_ERROR; let message = 'An unknown error occurred'; if (typeof error === 'string') { message = error; // Map common error messages to error codes if (error.includes('cancelled') || error.includes('canceled')) { code = exports.BiometricErrorCode.USER_CANCELLED; } else if (error.includes('failed') || error.includes('not recognized')) { code = exports.BiometricErrorCode.AUTHENTICATION_FAILED; } else if (error.includes('locked')) { code = exports.BiometricErrorCode.LOCKOUT; } } else if (error instanceof Error) { message = error.message; } return { code, message, details: error }; } generateSessionId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } } var ReactNativeAdapter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, ReactNativeAdapter: ReactNativeAdapter }); class ElectronAdapter { constructor() { this.platform = 'electron'; // Electron-specific initialization } async isAvailable() { try { // Check if we're in Electron main or renderer process if (typeof process !== 'undefined' && process.versions && process.versions.electron) { // In Electron, we can use TouchID on macOS if (process.platform === 'darwin') { // eslint-disable-next-line @typescript-eslint/no-require-imports const electronModule = require('electron'); const { systemPreferences } = electronModule.remote || electronModule; return systemPreferences.canPromptTouchID(); } // Windows Hello support could be added here return false; } return false; } catch (_a) { return false; } } async getSupportedBiometrics() { if (!(await this.isAvailable())) { return []; } // On macOS, we support Touch ID if (process.platform === 'darwin') { return [BiometryType.TOUCH_ID]; } return []; } async authenticate(options) { try { if (!(await this.isAvailable())) { return { success: false, error: { code: exports.BiometricErrorCode.BIOMETRIC_UNAVAILABLE, message: 'Biometric authentication is not available' } }; } if (process.platform === 'darwin') { // eslint-disable-next-line @typescript-eslint/no-require-imports const electronModule = require('electron'); const { systemPreferences } = electronModule.remote || electronModule; try { await systemPreferences.promptTouchID((options === null || options === void 0 ? void 0 : options.reason) || 'authenticate with Touch ID'); return { success: true, biometryType: BiometryType.TOUCH_ID, sessionId: this.generateSessionId(), platform: 'electron' }; } catch (_a) { return { success: false, error: { code: exports.BiometricErrorCode.AUTHENTICATION_FAILED, message: 'Touch ID authentication failed' } }; } } return { success: false, error: { code: exports.BiometricErrorCode.PLATFORM_NOT_SUPPORTED, message: 'Platform not supported' } }; } catch (error) { return { success: false, error: this.mapError(error) }; } } async deleteCredentials() { // Electron doesn't store biometric credentials // This is a no-op } async hasCredentials() { // In Electron, we don't store credentials // Return true if biometrics are available return await this.isAvailable(); } mapError(error) { let code = exports.BiometricErrorCode.UNKNOWN_ERROR; let message = 'An unknown error occurred'; if (error instanceof Error) { message = error.message; if (message.includes('cancelled') || message.includes('canceled')) { code = exports.BiometricErrorCode.USER_CANCELLED; } else if (message.includes('failed')) { code = exports.BiometricErrorCode.AUTHENTICATION_FAILED; } } return { code, message, details: error }; } generateSessionId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } } var ElectronAdapter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, ElectronAdapter: ElectronAdapter }); exports.BiometricAuth = BiometricAuth; exports.BiometricAuthCore = BiometricAuthCore; exports.CapacitorAdapter = CapacitorAdapter; exports.PlatformDetector = PlatformDetector; exports.WebAdapter = WebAdapter; exports.default = BiometricAuth; //# sourceMappingURL=plugin.cjs.js.map