UNPKG

@cabek/sdk

Version:

Official JavaScript/TypeScript SDK for C.A.B.E.K. biometric authentication

459 lines (454 loc) 15.5 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var axios = require('axios'); var WebSocket = require('ws'); var EventEmitter = require('events'); /** * C.A.B.E.K. JavaScript/TypeScript SDK * Official SDK for integrating C.A.B.E.K. biometric authentication * * @packageDocumentation */ /** * Authentication state enum */ exports.AuthenticationState = void 0; (function (AuthenticationState) { AuthenticationState["IDLE"] = "idle"; AuthenticationState["AUTHENTICATING"] = "authenticating"; AuthenticationState["AUTHENTICATED"] = "authenticated"; AuthenticationState["FAILED"] = "failed"; AuthenticationState["LOCKED"] = "locked"; })(exports.AuthenticationState || (exports.AuthenticationState = {})); /** * Device types supported by C.A.B.E.K. */ exports.DeviceType = void 0; (function (DeviceType) { DeviceType["SMARTWATCH"] = "smartwatch"; DeviceType["CHEST_STRAP"] = "chest_strap"; DeviceType["PATCH"] = "patch"; DeviceType["WEB"] = "web"; })(exports.DeviceType || (exports.DeviceType = {})); /** * Custom exceptions for C.A.B.E.K. SDK */ class CABEKException extends Error { constructor(message) { super(message); this.name = 'CABEKException'; } } class AuthenticationException extends CABEKException { constructor(message) { super(message); this.name = 'AuthenticationException'; } } class ConnectionException extends CABEKException { constructor(message) { super(message); this.name = 'ConnectionException'; } } class SignalQualityException extends CABEKException { constructor(message) { super(message); this.name = 'SignalQualityException'; } } /** * Main C.A.B.E.K. SDK Client * * @example * ```typescript * const cabek = new CABEK({ apiKey: 'sk_live_...' }); * const result = await cabek.authenticate(ecgData); * ``` */ class CABEK extends EventEmitter { /** * Create a new C.A.B.E.K. client * @param config - Configuration options */ constructor(config) { super(); this.state = exports.AuthenticationState.IDLE; this.connectedDevices = new Map(); this.config = { apiKey: config.apiKey, apiUrl: config.apiUrl || 'https://api.cabek.io', timeout: config.timeout || 30000, autoReconnect: config.autoReconnect !== false, debug: config.debug || false }; // Initialize axios client this.axios = axios.create({ baseURL: this.config.apiUrl, timeout: this.config.timeout, headers: { 'Authorization': `Bearer ${this.config.apiKey}`, 'Content-Type': 'application/json', 'X-SDK-Version': '1.0.0', 'X-SDK-Language': 'JavaScript' } }); // Add response interceptor for error handling this.axios.interceptors.response.use(response => response, this.handleAxiosError.bind(this)); } /** * Handle axios errors */ handleAxiosError(error) { if (error.response) { const status = error.response.status; const data = error.response.data; if (status === 401) { throw new AuthenticationException('Invalid API key'); } else if (status === 429) { throw new CABEKException('Rate limit exceeded'); } else if (status >= 400) { throw new CABEKException(`API error: ${data?.error || 'Unknown'}`); } } else if (error.request) { throw new ConnectionException('Connection failed: No response from server'); } throw new ConnectionException(`Connection failed: ${error.message}`); } /** * Generate a unique device ID */ generateDeviceId() { const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substring(2, 15); return `${timestamp}${random}`.substring(0, 16); } /** * Log debug messages */ debug(message, data) { if (this.config.debug) { console.log(`[CABEK SDK] ${message}`, data || ''); } } /** * Enroll a user for biometric authentication * * @param userId - Unique user identifier * @param ecgData - ECG signal data for enrollment * @param deviceId - Optional device identifier * @param deviceType - Type of device used * @returns Enrollment result */ async enroll(userId, ecgData, deviceId, deviceType = exports.DeviceType.WEB) { // Validate ECG data if (!ecgData || ecgData.length < 256) { throw new SignalQualityException('Insufficient ECG data for enrollment (minimum 256 samples)'); } const enrollmentData = { user_id: userId, ecg_data: ecgData.slice(0, 2048), // Use up to 8 seconds at 256Hz device_id: deviceId || this.generateDeviceId(), device_type: deviceType, timestamp_ns: Date.now() * 1000000 }; try { this.debug('Enrolling user', { userId, deviceType }); const response = await this.axios.post('/users/enroll', enrollmentData); const result = { success: response.data.enrolled || false, userId: response.data.user_id, deviceId: enrollmentData.device_id, message: response.data.message, error: response.data.error }; this.emit('enrollment', result); return result; } catch (error) { const result = { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; this.emit('enrollment', result); return result; } } /** * Authenticate using ECG biometric data * * @param ecgData - ECG signal data * @param userId - Optional user identifier * @param deviceId - Optional device identifier * @returns Authentication result */ async authenticate(ecgData, userId, deviceId) { this.state = exports.AuthenticationState.AUTHENTICATING; this.emit('stateChange', this.state); // Validate ECG data if (!ecgData || ecgData.length < 128) { this.state = exports.AuthenticationState.FAILED; this.emit('stateChange', this.state); throw new SignalQualityException('Insufficient ECG data (minimum 128 samples)'); } const authData = { ecg_data: ecgData.slice(0, 512), // Use up to 2 seconds at 256Hz user_id: userId, device_id: deviceId || this.generateDeviceId(), timestamp_ns: Date.now() * 1000000 }; try { this.debug('Authenticating', { userId, deviceId }); const response = await this.axios.post('/auth/verify', authData); const result = { success: response.data.authenticated || false, confidence: response.data.confidence || 0, ephemeralToken: response.data.ephemeral_token, expiresMs: response.data.expires_ms || 100, userId: response.data.user_id, deviceId: authData.device_id, error: response.data.error, timestamp: Date.now() * 1000000 }; if (result.success) { this.state = exports.AuthenticationState.AUTHENTICATED; this.emit('authenticated', result); } else { this.state = exports.AuthenticationState.FAILED; this.emit('authenticationFailed', result); } this.emit('stateChange', this.state); return result; } catch (error) { this.state = exports.AuthenticationState.FAILED; this.emit('stateChange', this.state); const result = { success: false, confidence: 0, expiresMs: 0, error: error instanceof Error ? error.message : 'Unknown error', timestamp: Date.now() * 1000000 }; this.emit('authenticationFailed', result); return result; } } /** * Start continuous authentication stream * * @param ecgStreamCallback - Async generator that yields ECG data * @param userId - User identifier * @param deviceId - Optional device identifier * @param authFrequencyHz - Authentication frequency in Hz */ async startContinuousAuth(ecgStreamCallback, userId, deviceId, authFrequencyHz = 10.0) { const finalDeviceId = deviceId || this.generateDeviceId(); // Create continuous authentication session const sessionData = { user_id: userId, device_id: finalDeviceId, auth_frequency_hz: authFrequencyHz }; const response = await this.axios.post('/auth/continuous/start', sessionData); this.currentSessionId = response.data.session_id; // Connect WebSocket for real-time updates const wsUrl = this.config.apiUrl .replace('http', 'ws') .replace('https', 'wss') + `/ws/auth/${this.currentSessionId}?api_key=${this.config.apiKey}`; this.websocket = new WebSocket(wsUrl); this.websocket.on('open', () => { this.debug('WebSocket connected'); this.emit('websocketConnected'); }); this.websocket.on('message', (data) => { try { const message = JSON.parse(data); this.emit('websocketMessage', message); } catch (error) { this.debug('Failed to parse WebSocket message', error); } }); this.websocket.on('error', (error) => { this.debug('WebSocket error', error); this.emit('websocketError', error); }); // Start authentication loop const authInterval = 1000 / authFrequencyHz; try { for await (const ecgData of ecgStreamCallback) { // Authenticate const result = await this.authenticate(ecgData, userId, finalDeviceId); // Send to WebSocket if connected if (this.websocket?.readyState === WebSocket.OPEN) { this.websocket.send(JSON.stringify({ type: 'auth_result', session_id: this.currentSessionId, result: result })); } // Check for lock condition if (!result.success) { this.state = exports.AuthenticationState.LOCKED; this.emit('locked', result); this.emit('stateChange', this.state); break; } // Wait for next interval await this.sleep(authInterval); } } finally { // End session if (this.currentSessionId) { await this.endContinuousAuth(); } } } /** * End continuous authentication session */ async endContinuousAuth() { if (!this.currentSessionId) { return; } try { await this.axios.post(`/auth/continuous/end/${this.currentSessionId}`); this.debug('Continuous auth session ended'); } catch (error) { this.debug('Failed to end continuous auth session', error); } // Close WebSocket if (this.websocket) { this.websocket.close(); this.websocket = undefined; } this.currentSessionId = undefined; } /** * Validate ECG signal quality * * @param ecgData - ECG signal data to validate * @returns Signal quality metrics */ async validateSignalQuality(ecgData) { const response = await this.axios.post('/signals/validate', { ecg_data: ecgData.slice(0, 512) }); return response.data; } /** * Get session statistics * * @param sessionId - Optional session ID (uses current if not provided) * @returns Session statistics */ async getSessionStats(sessionId) { const id = sessionId || this.currentSessionId; if (!id) { return null; } const response = await this.axios.get(`/auth/sessions/${id}/stats`); return response.data; } /** * Connect a biometric device * * @param deviceType - Type of device * @param connectionParams - Device-specific connection parameters * @returns Device information */ async connectDevice(deviceType, connectionParams) { const deviceId = connectionParams.device_id || this.generateDeviceId(); const device = { deviceId, deviceType, model: connectionParams.model, firmwareVersion: connectionParams.firmware_version, batteryLevel: connectionParams.battery_level, samplingRate: connectionParams.sampling_rate || 256, isConnected: true, lastSeen: new Date() }; this.connectedDevices.set(deviceId, device); this.emit('deviceConnected', device); return device; } /** * Disconnect a biometric device * * @param deviceId - Device identifier */ async disconnectDevice(deviceId) { const device = this.connectedDevices.get(deviceId); if (device) { device.isConnected = false; this.connectedDevices.delete(deviceId); this.emit('deviceDisconnected', device); } } /** * Get all connected devices */ getConnectedDevices() { return Array.from(this.connectedDevices.values()); } /** * Get current authentication state */ getState() { return this.state; } /** * Check if client is authenticated */ isAuthenticated() { return this.state === exports.AuthenticationState.AUTHENTICATED; } /** * Helper function to sleep */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Cleanup resources */ async cleanup() { await this.endContinuousAuth(); this.removeAllListeners(); this.connectedDevices.clear(); } } /** * Quick authentication helper * * @param apiKey - C.A.B.E.K. API key * @param ecgData - ECG signal data * @param userId - Optional user identifier * @returns Authentication result */ async function quickAuthenticate(apiKey, ecgData, userId) { const client = new CABEK({ apiKey }); try { return await client.authenticate(ecgData, userId); } finally { await client.cleanup(); } } exports.AuthenticationException = AuthenticationException; exports.CABEK = CABEK; exports.CABEKException = CABEKException; exports.ConnectionException = ConnectionException; exports.SignalQualityException = SignalQualityException; exports.default = CABEK; exports.quickAuthenticate = quickAuthenticate; //# sourceMappingURL=cabek-sdk.js.map