capacitor-biometric-authentication
Version:
Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!
1,211 lines (1,197 loc) • 50.1 kB
JavaScript
var BiometricAuth = (function (exports) {
'use strict';
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;
Object.defineProper