capacitor-biometric-authentication
Version:
Framework-agnostic biometric authentication library. Works with React, Vue, Angular, or vanilla JS. No providers required!
236 lines • 10.6 kB
JavaScript
import { BiometricErrorCode, BiometryType } from '../core/types';
export class CapacitorAdapter {
constructor() {
this.platform = 'capacitor';
this.capacitorPlugin = null;
// IMPORTANT: Use a flag to track initialization instead of checking plugin truthiness
// Capacitor's plugin proxy intercepts ALL property access including truthiness checks
// which can trigger ".then() is not implemented" errors
this.pluginInitialized = false;
// Plugin will be loaded dynamically
}
async getPlugin() {
var _a, _b, _c;
// Use flag-based check instead of checking plugin truthiness
// to avoid triggering Capacitor proxy's property interception
if (this.pluginInitialized) {
return this.capacitorPlugin;
}
try {
// Try to get the registered Capacitor plugin
const capacitorCore = await import('@capacitor/core');
// Get Capacitor global object for platform detection
const capacitorGlobal = capacitorCore.Capacitor;
// Check if we're on a native platform (Android/iOS)
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;
if (isNative) {
// CRITICAL: On native platforms, the plugin is ALREADY registered by Capacitor
// DO NOT call registerPlugin() again - it creates a broken proxy that throws
// ".then() is not implemented" errors when JavaScript checks for Promise-like objects
// Instead, get the reference from Capacitor.Plugins which has the working native bridge
const nativePlugin = (_c = capacitorGlobal === null || capacitorGlobal === void 0 ? void 0 : capacitorGlobal.Plugins) === null || _c === void 0 ? void 0 : _c['BiometricAuth'];
if (nativePlugin) {
this.capacitorPlugin = nativePlugin;
this.pluginInitialized = true;
return this.capacitorPlugin;
}
// If not in Plugins yet, it might still be initializing - throw to retry later
throw new Error('Native BiometricAuth plugin not yet registered');
}
// WEB ONLY: Use registerPlugin to create a web implementation
// This is safe on web because there's no native plugin to conflict with
if (capacitorCore.registerPlugin) {
try {
this.capacitorPlugin = capacitorCore.registerPlugin('BiometricAuth');
this.pluginInitialized = true;
return this.capacitorPlugin;
}
catch (_d) {
// Continue to fallback
}
}
// Legacy support for older Capacitor versions (web only)
const legacyPlugins = capacitorCore.Plugins;
if (legacyPlugins === null || legacyPlugins === void 0 ? void 0 : legacyPlugins.BiometricAuth) {
this.capacitorPlugin = legacyPlugins.BiometricAuth;
this.pluginInitialized = true;
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;
this.pluginInitialized = true;
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 = 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 = BiometricErrorCode.BIOMETRIC_UNAVAILABLE;
message = errorObj.message || 'Biometric authentication is not available';
break;
case 'USER_CANCELLED':
case 'CANCELLED':
case 'USER_CANCEL':
code = BiometricErrorCode.USER_CANCELLED;
message = errorObj.message || 'User cancelled authentication';
break;
case 'AUTHENTICATION_FAILED':
case 'FAILED':
code = BiometricErrorCode.AUTHENTICATION_FAILED;
message = errorObj.message || 'Authentication failed';
break;
case 'TIMEOUT':
code = BiometricErrorCode.TIMEOUT;
message = errorObj.message || 'Authentication timed out';
break;
case 'LOCKOUT':
code = BiometricErrorCode.LOCKOUT;
message = errorObj.message || 'Too many failed attempts';
break;
case 'NOT_ENROLLED':
code = 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);
}
}
//# sourceMappingURL=CapacitorAdapter.js.map