capacitor-biometric-authentication
Version:
Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!
1 lines • 104 kB
Source Map (JSON)
{"version":3,"file":"plugin.mjs","sources":["esm/types/errors.js","esm/types/results.js","esm/types/adapters.js","esm/core/platform-detector.js","esm/core/BiometricAuthCore.js","esm/utils/encoding.js","esm/utils/error-handler.js","esm/adapters/WebAdapter.js","esm/adapters/CapacitorAdapter.js","esm/index.js","esm/adapters/ElectronAdapter.js"],"sourcesContent":["/**\n * Unified error codes for biometric authentication\n * Using UPPER_CASE convention for enum values (more conventional)\n */\nexport var BiometricErrorCode;\n(function (BiometricErrorCode) {\n /** Authentication attempt failed */\n BiometricErrorCode[\"AUTHENTICATION_FAILED\"] = \"AUTHENTICATION_FAILED\";\n /** User cancelled the authentication */\n BiometricErrorCode[\"USER_CANCELLED\"] = \"USER_CANCELLED\";\n /** System cancelled the authentication */\n BiometricErrorCode[\"SYSTEM_CANCELLED\"] = \"SYSTEM_CANCELLED\";\n /** Biometric hardware not available */\n BiometricErrorCode[\"NOT_AVAILABLE\"] = \"NOT_AVAILABLE\";\n /** Biometric hardware unavailable (legacy alias) */\n BiometricErrorCode[\"BIOMETRIC_UNAVAILABLE\"] = \"BIOMETRIC_UNAVAILABLE\";\n /** Permission denied by user */\n BiometricErrorCode[\"PERMISSION_DENIED\"] = \"PERMISSION_DENIED\";\n /** User is locked out due to too many failed attempts */\n BiometricErrorCode[\"LOCKED_OUT\"] = \"LOCKED_OUT\";\n /** Lockout (legacy alias) */\n BiometricErrorCode[\"LOCKOUT\"] = \"LOCKOUT\";\n /** Invalid context for authentication */\n BiometricErrorCode[\"INVALID_CONTEXT\"] = \"INVALID_CONTEXT\";\n /** No biometrics enrolled on device */\n BiometricErrorCode[\"NOT_ENROLLED\"] = \"NOT_ENROLLED\";\n /** Authentication timed out */\n BiometricErrorCode[\"TIMEOUT\"] = \"TIMEOUT\";\n /** Platform not supported */\n BiometricErrorCode[\"PLATFORM_NOT_SUPPORTED\"] = \"PLATFORM_NOT_SUPPORTED\";\n /** Unknown error occurred */\n BiometricErrorCode[\"UNKNOWN\"] = \"UNKNOWN\";\n /** Unknown error (legacy alias) */\n BiometricErrorCode[\"UNKNOWN_ERROR\"] = \"UNKNOWN_ERROR\";\n})(BiometricErrorCode || (BiometricErrorCode = {}));\n/**\n * Reasons why biometric authentication is unavailable\n */\nexport var BiometricUnavailableReason;\n(function (BiometricUnavailableReason) {\n /** Device doesn't have biometric hardware */\n BiometricUnavailableReason[\"NO_HARDWARE\"] = \"noHardware\";\n /** Biometric hardware is unavailable */\n BiometricUnavailableReason[\"HARDWARE_UNAVAILABLE\"] = \"hardwareUnavailable\";\n /** No biometrics enrolled on device */\n BiometricUnavailableReason[\"NO_ENROLLED_BIOMETRICS\"] = \"noEnrolledBiometrics\";\n /** User denied permission */\n BiometricUnavailableReason[\"PERMISSION_DENIED\"] = \"permissionDenied\";\n /** Biometric not supported on this platform */\n BiometricUnavailableReason[\"NOT_SUPPORTED\"] = \"notSupported\";\n /** User locked out due to failed attempts */\n BiometricUnavailableReason[\"LOCKED_OUT\"] = \"lockedOut\";\n /** User disabled biometrics */\n BiometricUnavailableReason[\"USER_DISABLED\"] = \"userDisabled\";\n})(BiometricUnavailableReason || (BiometricUnavailableReason = {}));\n/**\n * Map legacy camelCase error codes to UPPER_CASE\n * Used for backward compatibility\n */\nexport const errorCodeMapping = {\n authenticationFailed: BiometricErrorCode.AUTHENTICATION_FAILED,\n userCancelled: BiometricErrorCode.USER_CANCELLED,\n systemCancelled: BiometricErrorCode.SYSTEM_CANCELLED,\n notAvailable: BiometricErrorCode.NOT_AVAILABLE,\n permissionDenied: BiometricErrorCode.PERMISSION_DENIED,\n lockedOut: BiometricErrorCode.LOCKED_OUT,\n invalidContext: BiometricErrorCode.INVALID_CONTEXT,\n notEnrolled: BiometricErrorCode.NOT_ENROLLED,\n timeout: BiometricErrorCode.TIMEOUT,\n unknown: BiometricErrorCode.UNKNOWN,\n};\n/**\n * Normalize error code to UPPER_CASE format\n */\nexport function normalizeErrorCode(code) {\n // Already in correct format\n if (Object.values(BiometricErrorCode).includes(code)) {\n return code;\n }\n // Map legacy format\n if (code in errorCodeMapping) {\n return errorCodeMapping[code];\n }\n return BiometricErrorCode.UNKNOWN;\n}\n//# sourceMappingURL=errors.js.map","/**\n * Types of biometric authentication available\n */\nexport var BiometryType;\n(function (BiometryType) {\n /** Fingerprint scanner */\n BiometryType[\"FINGERPRINT\"] = \"fingerprint\";\n /** Apple Face ID */\n BiometryType[\"FACE_ID\"] = \"faceId\";\n /** Apple Touch ID */\n BiometryType[\"TOUCH_ID\"] = \"touchId\";\n /** Iris scanner */\n BiometryType[\"IRIS\"] = \"iris\";\n /** Generic face authentication (Android) */\n BiometryType[\"FACE_AUTHENTICATION\"] = \"faceAuthentication\";\n /** Multiple biometric types available */\n BiometryType[\"MULTIPLE\"] = \"multiple\";\n /** Passcode/PIN fallback */\n BiometryType[\"PASSCODE\"] = \"passcode\";\n /** Pattern lock (Android) */\n BiometryType[\"PATTERN\"] = \"pattern\";\n /** PIN code */\n BiometryType[\"PIN\"] = \"pin\";\n /** Unknown biometric type */\n BiometryType[\"UNKNOWN\"] = \"unknown\";\n})(BiometryType || (BiometryType = {}));\n//# sourceMappingURL=results.js.map","/**\n * Fallback authentication methods\n */\nexport var FallbackMethod;\n(function (FallbackMethod) {\n FallbackMethod[\"PASSCODE\"] = \"passcode\";\n FallbackMethod[\"PASSWORD\"] = \"password\";\n FallbackMethod[\"PATTERN\"] = \"pattern\";\n FallbackMethod[\"PIN\"] = \"pin\";\n FallbackMethod[\"SECURITY_QUESTION\"] = \"securityQuestion\";\n})(FallbackMethod || (FallbackMethod = {}));\n//# sourceMappingURL=adapters.js.map","export class PlatformDetector {\n constructor() {\n this.platformInfo = null;\n }\n static getInstance() {\n if (!PlatformDetector.instance) {\n PlatformDetector.instance = new PlatformDetector();\n }\n return PlatformDetector.instance;\n }\n detect() {\n var _a, _b;\n if (this.platformInfo) {\n return this.platformInfo;\n }\n const info = {\n name: 'unknown',\n isCapacitor: false,\n isReactNative: false,\n isCordova: false,\n isWeb: false,\n isIOS: false,\n isAndroid: false,\n isElectron: false\n };\n // Check if we're in a browser environment\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n info.isWeb = true;\n // Check for Capacitor\n if (window.Capacitor) {\n info.isCapacitor = true;\n const capacitor = window.Capacitor;\n const platform = (_a = capacitor === null || capacitor === void 0 ? void 0 : capacitor.getPlatform) === null || _a === void 0 ? void 0 : _a.call(capacitor);\n if (platform) {\n info.name = platform;\n info.isIOS = platform === 'ios';\n info.isAndroid = platform === 'android';\n info.isWeb = platform === 'web';\n }\n }\n // Check for Cordova\n else if (window.cordova || window.phonegap) {\n info.isCordova = true;\n info.name = 'cordova';\n const userAgent = navigator.userAgent || navigator.vendor || window.opera || '';\n if (/android/i.test(userAgent)) {\n info.isAndroid = true;\n info.name = 'android';\n }\n else if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {\n info.isIOS = true;\n info.name = 'ios';\n }\n }\n // Check for Electron\n else if (((_b = window.process) === null || _b === void 0 ? void 0 : _b.type) === 'renderer' || navigator.userAgent.indexOf('Electron') !== -1) {\n info.isElectron = true;\n info.name = 'electron';\n }\n // Default to web\n else {\n info.name = 'web';\n }\n }\n // Node.js environment\n else if (typeof process !== 'undefined' && process.versions && process.versions.node) {\n info.name = 'node';\n }\n // Get version info if available\n if (info.isCapacitor && typeof window !== 'undefined') {\n const capacitor = window.Capacitor;\n if (capacitor === null || capacitor === void 0 ? void 0 : capacitor.version) {\n info.version = capacitor.version;\n }\n }\n this.platformInfo = info;\n return info;\n }\n isSupported() {\n const info = this.detect();\n return info.isWeb || info.isCapacitor || info.isCordova;\n }\n getPlatformName() {\n return this.detect().name;\n }\n isNativePlatform() {\n const info = this.detect();\n return (info.isIOS || info.isAndroid) && (info.isCapacitor || info.isCordova);\n }\n}\n//# sourceMappingURL=platform-detector.js.map","import { BiometricErrorCode } from './types';\nimport { PlatformDetector } from './platform-detector';\nexport class BiometricAuthCore {\n constructor() {\n this.config = {\n adapter: 'auto',\n debug: false,\n sessionDuration: 3600, // 1 hour in seconds (consistent with web.ts)\n };\n this.sessionTimeoutId = null;\n this.adapters = new Map();\n this.currentAdapter = null;\n this.state = {\n isAuthenticated: false\n };\n this.platformDetector = PlatformDetector.getInstance();\n this.subscribers = new Set();\n // Store the promise so we can await it in methods\n this.initPromise = this.initialize().catch(err => {\n if (this.config.debug) {\n console.warn('[BiometricAuth] Initialization failed:', err);\n }\n });\n }\n /**\n * Ensures the adapter is initialized before use.\n * This prevents race conditions where methods are called before\n * the async initialization completes.\n */\n async ensureInitialized() {\n await this.initPromise;\n }\n static getInstance() {\n if (!BiometricAuthCore.instance) {\n BiometricAuthCore.instance = new BiometricAuthCore();\n }\n return BiometricAuthCore.instance;\n }\n async initialize() {\n // Detect platform and load appropriate adapter\n const platformInfo = this.platformDetector.detect();\n if (this.config.debug) {\n console.warn('[BiometricAuth] Platform detected:', platformInfo);\n }\n // Load adapter based on platform\n await this.loadAdapter(platformInfo.name);\n }\n async loadAdapter(platform) {\n var _a;\n try {\n // Try custom adapters first\n if ((_a = this.config.customAdapters) === null || _a === void 0 ? void 0 : _a[platform]) {\n this.currentAdapter = this.config.customAdapters[platform];\n return;\n }\n // Try to load from registered adapters\n if (this.adapters.has(platform)) {\n this.currentAdapter = this.adapters.get(platform);\n return;\n }\n // Dynamic import based on platform\n switch (platform) {\n case 'web':\n const { WebAdapter } = await import('../adapters/WebAdapter');\n this.currentAdapter = new WebAdapter();\n break;\n case 'ios':\n case 'android':\n // Check if Capacitor is available\n if (this.platformDetector.detect().isCapacitor) {\n const { CapacitorAdapter } = await import('../adapters/CapacitorAdapter');\n this.currentAdapter = new CapacitorAdapter();\n }\n else if (this.platformDetector.detect().isCordova) {\n // For Cordova, we might need a separate adapter in the future\n throw new Error('Cordova support not yet implemented. Please use Capacitor for native biometric authentication.');\n }\n break;\n case 'electron':\n const { ElectronAdapter } = await import('../adapters/ElectronAdapter');\n this.currentAdapter = new ElectronAdapter();\n break;\n default:\n throw new Error(`Platform ${platform} not supported`);\n }\n }\n catch (error) {\n if (this.config.debug) {\n console.warn('[BiometricAuth] Failed to load adapter:', error);\n }\n // Fallback to web adapter if available\n if (platform !== 'web' && this.platformDetector.detect().isWeb) {\n const { WebAdapter } = await import('../adapters/WebAdapter');\n this.currentAdapter = new WebAdapter();\n }\n }\n }\n configure(config) {\n this.config = Object.assign(Object.assign({}, this.config), config);\n // Re-initialize if adapter changed\n if (config.adapter && config.adapter !== 'auto') {\n this.loadAdapter(config.adapter);\n }\n }\n registerAdapter(name, adapter) {\n this.adapters.set(name, adapter);\n }\n async isAvailable() {\n await this.ensureInitialized();\n if (!this.currentAdapter) {\n return false;\n }\n try {\n return await this.currentAdapter.isAvailable();\n }\n catch (error) {\n if (this.config.debug) {\n console.warn('[BiometricAuth] isAvailable error:', error);\n }\n return false;\n }\n }\n async getSupportedBiometrics() {\n await this.ensureInitialized();\n if (!this.currentAdapter) {\n return [];\n }\n try {\n return await this.currentAdapter.getSupportedBiometrics();\n }\n catch (error) {\n if (this.config.debug) {\n console.warn('[BiometricAuth] getSupportedBiometrics error:', error);\n }\n return [];\n }\n }\n async authenticate(options) {\n await this.ensureInitialized();\n if (!this.currentAdapter) {\n return {\n success: false,\n error: {\n code: BiometricErrorCode.PLATFORM_NOT_SUPPORTED,\n message: 'No biometric adapter available for this platform'\n }\n };\n }\n try {\n const result = await this.currentAdapter.authenticate(options);\n if (result.success) {\n this.updateState({\n isAuthenticated: true,\n sessionId: result.sessionId,\n lastAuthTime: Date.now(),\n biometryType: result.biometryType,\n error: undefined\n });\n // Set up session timeout\n if (this.config.sessionDuration && this.config.sessionDuration > 0) {\n // Clear any existing timeout to prevent memory leaks\n if (this.sessionTimeoutId !== null) {\n clearTimeout(this.sessionTimeoutId);\n }\n // Convert seconds to milliseconds for setTimeout\n this.sessionTimeoutId = setTimeout(() => {\n this.logout();\n }, this.config.sessionDuration * 1000);\n }\n }\n else {\n this.updateState({\n isAuthenticated: false,\n error: result.error\n });\n }\n return result;\n }\n catch (error) {\n const errorResult = {\n success: false,\n error: {\n code: BiometricErrorCode.UNKNOWN_ERROR,\n message: error instanceof Error ? error.message : 'Unknown error occurred',\n details: error\n }\n };\n this.updateState({\n isAuthenticated: false,\n error: errorResult.error\n });\n return errorResult;\n }\n }\n async deleteCredentials() {\n await this.ensureInitialized();\n if (!this.currentAdapter) {\n throw new Error('No biometric adapter available');\n }\n await this.currentAdapter.deleteCredentials();\n this.logout();\n }\n async hasCredentials() {\n await this.ensureInitialized();\n if (!this.currentAdapter) {\n return false;\n }\n try {\n return await this.currentAdapter.hasCredentials();\n }\n catch (error) {\n if (this.config.debug) {\n console.warn('[BiometricAuth] hasCredentials error:', error);\n }\n return false;\n }\n }\n logout() {\n // Clear session timeout to prevent memory leaks\n if (this.sessionTimeoutId !== null) {\n clearTimeout(this.sessionTimeoutId);\n this.sessionTimeoutId = null;\n }\n this.updateState({\n isAuthenticated: false,\n sessionId: undefined,\n lastAuthTime: undefined,\n biometryType: undefined,\n error: undefined\n });\n }\n getState() {\n return Object.assign({}, this.state);\n }\n isAuthenticated() {\n if (!this.state.isAuthenticated || !this.state.lastAuthTime) {\n return false;\n }\n // Check if session is still valid (sessionDuration is in seconds)\n if (this.config.sessionDuration && this.config.sessionDuration > 0) {\n const elapsedMs = Date.now() - this.state.lastAuthTime;\n const sessionDurationMs = this.config.sessionDuration * 1000;\n if (elapsedMs > sessionDurationMs) {\n this.logout();\n return false;\n }\n }\n return true;\n }\n subscribe(callback) {\n this.subscribers.add(callback);\n // Return unsubscribe function\n return () => {\n this.subscribers.delete(callback);\n };\n }\n updateState(newState) {\n this.state = Object.assign(Object.assign({}, this.state), newState);\n // Notify subscribers\n this.subscribers.forEach(callback => {\n callback(this.getState());\n });\n }\n // Utility methods for common use cases\n async requireAuthentication(callback, options) {\n var _a;\n if (!this.isAuthenticated()) {\n const result = await this.authenticate(options);\n if (!result.success) {\n throw new Error(((_a = result.error) === null || _a === void 0 ? void 0 : _a.message) || 'Authentication failed');\n }\n }\n return callback();\n }\n async withAuthentication(callback, options) {\n var _a;\n if (!this.isAuthenticated()) {\n const result = await this.authenticate(options);\n if (!result.success) {\n throw new Error(((_a = result.error) === null || _a === void 0 ? void 0 : _a.message) || 'Authentication failed');\n }\n }\n return callback();\n }\n}\n//# sourceMappingURL=BiometricAuthCore.js.map","/**\n * Unified encoding utilities for biometric authentication\n *\n * This module consolidates base64/base64url encoding functions\n * that were previously duplicated across multiple files.\n */\n/**\n * Convert ArrayBuffer to standard base64 string\n */\nexport function arrayBufferToBase64(buffer) {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n}\n/**\n * Convert ArrayBuffer to base64url string (WebAuthn standard)\n * Base64url is URL-safe: uses - instead of +, _ instead of /, no padding\n */\nexport function arrayBufferToBase64URL(buffer) {\n return arrayBufferToBase64(buffer)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n}\n/**\n * Convert base64 string to ArrayBuffer\n */\nexport function base64ToArrayBuffer(base64) {\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n}\n/**\n * Convert base64url string to ArrayBuffer\n */\nexport function base64URLToArrayBuffer(base64url) {\n // Convert base64url to standard base64\n let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');\n // Add padding if necessary\n const paddingNeeded = (4 - (base64.length % 4)) % 4;\n base64 = base64.padEnd(base64.length + paddingNeeded, '=');\n return base64ToArrayBuffer(base64);\n}\n/**\n * Convert Uint8Array to base64url string\n */\nexport function uint8ArrayToBase64URL(array) {\n // Create a new ArrayBuffer to avoid SharedArrayBuffer issues\n const buffer = new ArrayBuffer(array.byteLength);\n new Uint8Array(buffer).set(array);\n return arrayBufferToBase64URL(buffer);\n}\n/**\n * Convert base64url string to Uint8Array\n */\nexport function base64URLToUint8Array(base64url) {\n return new Uint8Array(base64URLToArrayBuffer(base64url));\n}\n/**\n * Convert various input formats to ArrayBuffer\n * Handles ArrayBuffer, Uint8Array, base64, base64url, and UTF-8 strings\n */\nexport function toArrayBuffer(data) {\n if (!data)\n return undefined;\n if (data instanceof ArrayBuffer) {\n return data;\n }\n if (data instanceof Uint8Array) {\n const buffer = data.buffer;\n return buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);\n }\n if (typeof data === 'string') {\n // Try base64url first (WebAuthn standard)\n try {\n return base64URLToArrayBuffer(data);\n }\n catch (_a) {\n // Try standard base64\n try {\n return base64ToArrayBuffer(data);\n }\n catch (_b) {\n // Fallback to UTF-8 encoding\n return new TextEncoder().encode(data).buffer;\n }\n }\n }\n return undefined;\n}\n/**\n * Generate a cryptographically secure random session ID\n */\nexport function generateSessionId() {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return arrayBufferToBase64URL(array.buffer);\n}\n/**\n * Generate a cryptographically secure random challenge\n */\nexport function generateChallenge(length = 32) {\n const challenge = new Uint8Array(length);\n crypto.getRandomValues(challenge);\n return challenge.buffer;\n}\n/**\n * Check if a string is valid base64url\n */\nexport function isBase64URL(str) {\n return /^[A-Za-z0-9_-]*$/.test(str);\n}\n/**\n * Check if a string is valid base64\n */\nexport function isBase64(str) {\n return /^[A-Za-z0-9+/]*={0,2}$/.test(str);\n}\n//# sourceMappingURL=encoding.js.map","/**\n * Unified error handling utilities for biometric authentication\n *\n * This module consolidates error mapping logic from multiple adapters\n * to provide consistent error handling across platforms.\n */\nimport { BiometricErrorCode, } from '../types';\n/**\n * Map a WebAuthn DOMException to a BiometricError\n */\nexport function mapDOMException(error) {\n switch (error.name) {\n case 'NotAllowedError':\n return {\n code: BiometricErrorCode.USER_CANCELLED,\n message: 'User cancelled the authentication',\n details: error,\n };\n case 'AbortError':\n return {\n code: BiometricErrorCode.USER_CANCELLED,\n message: 'Authentication was aborted',\n details: error,\n };\n case 'SecurityError':\n return {\n code: BiometricErrorCode.AUTHENTICATION_FAILED,\n message: 'Security error during authentication',\n details: error,\n };\n case 'InvalidStateError':\n return {\n code: BiometricErrorCode.AUTHENTICATION_FAILED,\n message: 'Invalid state for authentication',\n details: error,\n };\n case 'NotSupportedError':\n return {\n code: BiometricErrorCode.BIOMETRIC_UNAVAILABLE,\n message: 'WebAuthn is not supported',\n details: error,\n };\n case 'TimeoutError':\n return {\n code: BiometricErrorCode.TIMEOUT,\n message: 'Authentication timed out',\n details: error,\n };\n case 'ConstraintError':\n return {\n code: BiometricErrorCode.AUTHENTICATION_FAILED,\n message: 'Authenticator constraint not satisfied',\n details: error,\n };\n default:\n return {\n code: BiometricErrorCode.UNKNOWN_ERROR,\n message: error.message || 'An unknown DOM error occurred',\n details: error,\n };\n }\n}\n/**\n * Map a generic Error to a BiometricError\n */\nexport function mapGenericError(error) {\n const message = error.message.toLowerCase();\n // Check for common error patterns\n if (message.includes('cancelled') || message.includes('canceled')) {\n return {\n code: BiometricErrorCode.USER_CANCELLED,\n message: 'User cancelled the operation',\n details: error,\n };\n }\n if (message.includes('timeout') || message.includes('timed out')) {\n return {\n code: BiometricErrorCode.TIMEOUT,\n message: 'Operation timed out',\n details: error,\n };\n }\n if (message.includes('not available') || message.includes('unavailable')) {\n return {\n code: BiometricErrorCode.BIOMETRIC_UNAVAILABLE,\n message: 'Biometric authentication is not available',\n details: error,\n };\n }\n if (message.includes('not supported')) {\n return {\n code: BiometricErrorCode.PLATFORM_NOT_SUPPORTED,\n message: 'Operation is not supported on this platform',\n details: error,\n };\n }\n if (message.includes('not enrolled') || message.includes('no biometrics')) {\n return {\n code: BiometricErrorCode.NOT_ENROLLED,\n message: 'No biometrics enrolled on this device',\n details: error,\n };\n }\n if (message.includes('locked out') || message.includes('lockout')) {\n return {\n code: BiometricErrorCode.LOCKED_OUT,\n message: 'Biometric authentication is locked out due to too many attempts',\n details: error,\n };\n }\n return {\n code: BiometricErrorCode.UNKNOWN_ERROR,\n message: error.message || 'An unknown error occurred',\n details: error,\n };\n}\n/**\n * Map any unknown error to a BiometricError\n */\nexport function mapWebAuthnError(error) {\n if (error instanceof DOMException) {\n return mapDOMException(error);\n }\n if (error instanceof Error) {\n return mapGenericError(error);\n }\n // Handle string errors\n if (typeof error === 'string') {\n return {\n code: BiometricErrorCode.UNKNOWN_ERROR,\n message: error,\n details: error,\n };\n }\n // Handle completely unknown errors\n return {\n code: BiometricErrorCode.UNKNOWN_ERROR,\n message: 'An unknown error occurred',\n details: error,\n };\n}\n/**\n * Map a native platform error (iOS/Android/Windows) to a BiometricError\n */\nexport function mapNativeError(error, platform) {\n const message = error.message.toLowerCase();\n // Windows/Electron specific error messages\n if (platform === 'windows' || platform === 'electron') {\n // Windows Hello cancellation\n if (message.includes('cancelled') || message.includes('canceled') || message.includes('user refused')) {\n return {\n code: BiometricErrorCode.USER_CANCELLED,\n message: 'User cancelled Windows Hello authentication',\n details: error,\n };\n }\n // Windows Hello not configured\n if (message.includes('not configured') || message.includes('not set up')) {\n return {\n code: BiometricErrorCode.NOT_ENROLLED,\n message: 'Windows Hello is not configured',\n details: error,\n };\n }\n // Windows Hello not available\n if (message.includes('not available') || message.includes('not supported')) {\n return {\n code: BiometricErrorCode.NOT_AVAILABLE,\n message: 'Windows Hello is not available on this device',\n details: error,\n };\n }\n // Windows Hello lockout\n if (message.includes('locked') || message.includes('too many attempts')) {\n return {\n code: BiometricErrorCode.LOCKED_OUT,\n message: 'Windows Hello is locked due to too many attempts',\n details: error,\n };\n }\n }\n // iOS specific error messages\n if (platform === 'ios') {\n if (message.includes('user cancel') || message.includes('userCancel')) {\n return {\n code: BiometricErrorCode.USER_CANCELLED,\n message: 'User cancelled authentication',\n details: error,\n };\n }\n if (message.includes('passcode not set') || message.includes('passcodeNotSet')) {\n return {\n code: BiometricErrorCode.NOT_ENROLLED,\n message: 'Passcode is not set on this device',\n details: error,\n };\n }\n if (message.includes('biometry not available') || message.includes('biometryNotAvailable')) {\n return {\n code: BiometricErrorCode.NOT_AVAILABLE,\n message: 'Biometry is not available on this device',\n details: error,\n };\n }\n if (message.includes('biometry not enrolled') || message.includes('biometryNotEnrolled')) {\n return {\n code: BiometricErrorCode.NOT_ENROLLED,\n message: 'No biometrics are enrolled on this device',\n details: error,\n };\n }\n if (message.includes('biometry lockout') || message.includes('biometryLockout')) {\n return {\n code: BiometricErrorCode.LOCKED_OUT,\n message: 'Biometric authentication is locked out',\n details: error,\n };\n }\n }\n // Android specific error messages\n if (platform === 'android') {\n if (message.includes('ERROR_USER_CANCELED') || message.includes('user canceled')) {\n return {\n code: BiometricErrorCode.USER_CANCELLED,\n message: 'User cancelled authentication',\n details: error,\n };\n }\n if (message.includes('ERROR_NO_BIOMETRICS') || message.includes('no biometrics')) {\n return {\n code: BiometricErrorCode.NOT_ENROLLED,\n message: 'No biometrics are enrolled on this device',\n details: error,\n };\n }\n if (message.includes('ERROR_HW_NOT_PRESENT') || message.includes('hw not present')) {\n return {\n code: BiometricErrorCode.NOT_AVAILABLE,\n message: 'Biometric hardware is not available',\n details: error,\n };\n }\n if (message.includes('ERROR_HW_UNAVAILABLE') || message.includes('hw unavailable')) {\n return {\n code: BiometricErrorCode.NOT_AVAILABLE,\n message: 'Biometric hardware is currently unavailable',\n details: error,\n };\n }\n if (message.includes('ERROR_LOCKOUT') || message.includes('lockout')) {\n return {\n code: BiometricErrorCode.LOCKED_OUT,\n message: 'Biometric authentication is locked out',\n details: error,\n };\n }\n if (message.includes('ERROR_NEGATIVE_BUTTON') || message.includes('negative button')) {\n return {\n code: BiometricErrorCode.USER_CANCELLED,\n message: 'User pressed the negative button',\n details: error,\n };\n }\n }\n // Fall back to generic error mapping\n return mapGenericError(error);\n}\n/**\n * Create a failed BiometricAuthResult from an error\n */\nexport function createErrorResult(error, platform) {\n let biometricError;\n if (platform && error instanceof Error) {\n biometricError = mapNativeError(error, platform);\n }\n else {\n biometricError = mapWebAuthnError(error);\n }\n return {\n success: false,\n error: biometricError,\n };\n}\n/**\n * Check if an error is a user cancellation\n */\nexport function isUserCancellation(error) {\n if (error instanceof DOMException) {\n return error.name === 'NotAllowedError' || error.name === 'AbortError';\n }\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n return message.includes('cancel') || message.includes('abort');\n }\n return false;\n}\n/**\n * Check if an error is a timeout\n */\nexport function isTimeout(error) {\n if (error instanceof DOMException) {\n return error.name === 'TimeoutError';\n }\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n return message.includes('timeout') || message.includes('timed out');\n }\n return false;\n}\n/**\n * Check if an error indicates biometrics are unavailable\n */\nexport function isBiometricUnavailable(error) {\n if (error instanceof DOMException) {\n return error.name === 'NotSupportedError';\n }\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n return (message.includes('not available') ||\n message.includes('unavailable') ||\n message.includes('not supported'));\n }\n return false;\n}\n//# sourceMappingURL=error-handler.js.map","import { BiometricErrorCode, BiometryType } from '../core/types';\nimport { arrayBufferToBase64, base64ToArrayBuffer, generateSessionId as generateSecureSessionId, } from '../utils/encoding';\nimport { createErrorResult } from '../utils/error-handler';\nexport class WebAdapter {\n constructor() {\n this.platform = 'web';\n this.credentials = new Map();\n // Set default Relying Party info\n this.rpId = window.location.hostname;\n this.rpName = document.title || 'Biometric Authentication';\n }\n async isAvailable() {\n // Check if WebAuthn is supported\n if (!window.PublicKeyCredential) {\n return false;\n }\n // Check if platform authenticator is available\n try {\n const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();\n return available;\n }\n catch (_a) {\n return false;\n }\n }\n async getSupportedBiometrics() {\n if (!(await this.isAvailable())) {\n return [];\n }\n // WebAuthn doesn't provide specific biometry types\n // Return generic \"multiple\" as modern devices support various methods\n return [BiometryType.MULTIPLE];\n }\n async authenticate(options) {\n var _a;\n try {\n // Check if WebAuthn is available\n if (!(await this.isAvailable())) {\n return {\n success: false,\n error: {\n code: BiometricErrorCode.BIOMETRIC_UNAVAILABLE,\n message: 'WebAuthn is not available on this device'\n }\n };\n }\n const webOptions = ((_a = options === null || options === void 0 ? void 0 : options.platform) === null || _a === void 0 ? void 0 : _a.web) || {};\n // Try to get existing credential first\n const existingCredential = await this.getExistingCredential(webOptions);\n if (existingCredential) {\n return {\n success: true,\n biometryType: BiometryType.MULTIPLE,\n sessionId: generateSecureSessionId(),\n platform: 'web'\n };\n }\n // If no existing credential, create a new one\n const credential = await this.createCredential((options === null || options === void 0 ? void 0 : options.reason) || 'Authentication required', webOptions);\n if (credential) {\n // Store credential for future use\n const credentialId = arrayBufferToBase64(credential.rawId);\n this.credentials.set(credentialId, credential);\n this.saveCredentialId(credentialId);\n return {\n success: true,\n biometryType: BiometryType.MULTIPLE,\n sessionId: generateSecureSessionId(),\n platform: 'web'\n };\n }\n return {\n success: false,\n error: {\n code: BiometricErrorCode.AUTHENTICATION_FAILED,\n message: 'Failed to authenticate'\n }\n };\n }\n catch (error) {\n return createErrorResult(error);\n }\n }\n async deleteCredentials() {\n this.credentials.clear();\n localStorage.removeItem('biometric_credential_ids');\n }\n async hasCredentials() {\n const storedIds = this.getStoredCredentialIds();\n return storedIds.length > 0;\n }\n async getExistingCredential(options) {\n const storedIds = this.getStoredCredentialIds();\n if (storedIds.length === 0) {\n return null;\n }\n try {\n const challenge = options.challenge || crypto.getRandomValues(new Uint8Array(32));\n const publicKeyOptions = {\n challenge,\n rpId: options.rpId || this.rpId,\n timeout: options.timeout || 60000,\n userVerification: options.userVerification || 'preferred',\n allowCredentials: storedIds.map(id => ({\n id: base64ToArrayBuffer(id),\n type: 'public-key'\n }))\n };\n const credential = await navigator.credentials.get({\n publicKey: publicKeyOptions\n });\n return credential;\n }\n catch (_a) {\n return null;\n }\n }\n async createCredential(_reason, options) {\n try {\n const challenge = options.challenge || crypto.getRandomValues(new Uint8Array(32));\n const userId = crypto.getRandomValues(new Uint8Array(32));\n const publicKeyOptions = {\n challenge,\n rp: {\n id: options.rpId || this.rpId,\n name: options.rpName || this.rpName\n },\n user: {\n id: userId,\n name: 'user@' + this.rpId,\n displayName: 'User'\n },\n pubKeyCredParams: [\n { type: 'public-key', alg: -7 }, // ES256\n { type: 'public-key', alg: -257 } // RS256\n ],\n authenticatorSelection: options.authenticatorSelection || {\n authenticatorAttachment: 'platform',\n userVerification: 'preferred',\n requireResidentKey: false,\n residentKey: 'discouraged'\n },\n timeout: options.timeout || 60000,\n attestation: options.attestation || 'none'\n };\n const credential = await navigator.credentials.create({\n publicKey: publicKeyOptions\n });\n return credential;\n }\n catch (_a) {\n return null;\n }\n }\n getStoredCredentialIds() {\n const stored = localStorage.getItem('biometric_credential_ids');\n if (!stored) {\n return [];\n }\n try {\n return JSON.parse(stored);\n }\n catch (_a) {\n return [];\n }\n }\n saveCredentialId(id) {\n const existing = this.getStoredCredentialIds();\n if (!existing.includes(id)) {\n existing.push(id);\n localStorage.setItem('biometric_credential_ids', JSON.stringify(existing));\n }\n }\n}\n//# sourceMappingURL=WebAdapter.js.map","import { BiometricErrorCode, BiometryType } from '../core/types';\nexport class CapacitorAdapter {\n constructor() {\n this.platform = 'capacitor';\n this.capacitorPlugin = null;\n // IMPORTANT: Use a flag to track initialization instead of checking plugin truthiness\n // Capacitor's plugin proxy intercepts ALL property access including truthiness checks\n // which can trigger \".then() is not implemented\" errors\n this.pluginInitialized = false;\n // Plugin will be loaded dynamically\n }\n async getPlugin() {\n var _a, _b, _c;\n // Use flag-based check instead of checking plugin truthiness\n // to avoid triggering Capacitor proxy's property interception\n if (this.pluginInitialized) {\n return this.capacitorPlugin;\n }\n try {\n // Try to get the registered Capacitor plugin\n const capacitorCore = await import('@capacitor/core');\n // Get Capacitor global object for platform detection\n const capacitorGlobal = capacitorCore.Capacitor;\n // Check if we're on a native platform (Android/iOS)\n const isNative = (_b = (_a = capacitorGlobal === null || capacitorGlobal === void 0 ? void 0 : capacitorGlobal.isNativePlatform) === null || _a === void 0 ? void 0 : _a.call(capacitorGlobal)) !== null && _b !== void 0 ? _b : false;\n if (isNative) {\n // CRITICAL: On native platforms, the plugin is ALREADY registered by Capacitor\n // DO NOT call registerPlugin() again - it creates a broken proxy that throws\n // \".then() is not implemented\" errors when JavaScript checks for Promise-like objects\n // Instead, get the reference from Capacitor.Plugins which has the working native bridge\n const nativePlugin = (_c = capacitorGlobal === null || capacitorGlobal === void 0 ? void 0 : capacitorGlobal.Plugins) === null || _c === void 0 ? void 0 : _c['BiometricAuth'];\n if (nativePlugin) {\n this.capacitorPlugin = nativePlugin;\n this.pluginInitialized = true;\n return this.capacitorPlugin;\n }\n // If not in Plugins yet, it might still be initializing - throw to retry later\n throw new Error('Native BiometricAuth plugin not yet registered');\n }\n // WEB ONLY: Use registerPlugin to create a web implementation\n // This is safe on web because there's no native plugin to conflict with\n if (capacitorCore.registerPlugin) {\n try {\n this.capacitorPlugin = capacitorCore.registerPlugin('BiometricAuth');\n this.pluginInitialized = true;\n return this.capacitorPlugin;\n }\n catch (_d) {\n // Continue to fallback\n }\n }\n // Legacy support for older Capacitor versions (web only)\n const legacyPlugins = capacitorCore.Plugins;\n if (legacyPlugins === null || legacyPlugins === void 0 ? void 0 : legacyPlugins.BiometricAuth) {\n this.capacitorPlugin = legacyPlugins.BiometricAuth;\n this.pluginInitialized = true;\n return this.capacitorPlugin;\n }\n // If not found in Plugins, try direct import\n // This allows the plugin to work even if not properly registered\n const BiometricAuthPlugin = window.BiometricAuthPlugin;\n if (BiometricAuthPlugin) {\n this.capacitorPlugin = BiometricAuthPlugin;\n this.pluginInitialized = true;\n return this.capacitorPlugin;\n }\n throw new Error('BiometricAuth Capacitor plugin not found');\n }\n catch (error) {\n throw new Error('Failed to load Capacitor plugin: ' + error.message);\n }\n }\n async isAvailable() {\n try {\n const plugin = await this.getPlugin();\n const result = await plugin.isAvailable();\n return result.isAvailable || false;\n }\n catch (_a) {\n return false;\n }\n }\n async getSupportedBiometrics() {\n try {\n const plugin = await this.getPlugin();\n const result = await plugin.getSupportedBiometrics();\n // Map Capacitor biometry types to our types\n return (result.biometryTypes || []).map((type) => {\n switch (type.toLowerCase()) {\n case 'fingerprint':\n return BiometryType.FINGERPRINT;\n case 'faceid':\n case 'face_id':\n return BiometryType.FACE_ID;\n case 'touchid':\n case 'touch_id':\n return BiometryType.TOUCH_ID;\n case 'iris':\n return BiometryType.IRIS;\n default:\n return BiometryType.UNKNOWN;\n }\n }).filter((type) => type !== BiometryType.UNKNOWN);\n }\n catch (_a) {\n return [];\n }\n }\n async authenticate(options) {\n var _a, _b;\n try {\n const plugin = await this.getPlugin();\n // Map our options to Capacitor plugin options\n 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) || {}));\n const result = await plugin.authenticate(capacitorOptions);\n if (result.success) {\n const biometryType = this.mapBiometryType(result.biometryType);\n return {\n success: true,\n biometryType,\n sessionId: this.generateSessionId(),\n platform: 'capacitor'\n };\n }\n else {\n return {\n success: false,\n error: this.mapError(result.error)\n };\n }\n }\n catch (error) {\n return {\n success: false,\n error: this.mapError(error)\n };\n }\n }\n async deleteCredentials() {\n try {\n const plugin = await this.getPlugin();\n await plugin.deleteCredentials();\n }\n catch (_a) {\n // Ignore errors when deleting credentials\n }\n }\n async hasCredentials() {\n try {\n const plugin = await this.getPlugin();\n // Check if the plugin has a hasCredentials method\n if (typeof plugin.hasCredentials === 'function') {\n const result = await plugin.hasCredentials();\n return result.hasCredentials || false;\n }\n // Fallback: assume creden