UNPKG

@sfourdrinier/react-native-ble-plx

Version:
293 lines (265 loc) 8.66 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReconnectionManager = void 0; /** * Reconnection state for a device */ /** * Event callbacks for reconnection events */ /** * ReconnectionManager automatically handles reconnection when devices disconnect unexpectedly. * * Features: * - Automatic reconnection with exponential backoff * - Configurable retry limits and delays * - Event callbacks for monitoring reconnection status * - Per-device configuration * * @example * const reconnectionManager = new ReconnectionManager(bleManager); * * // Enable auto-reconnect for a device * reconnectionManager.enableAutoReconnect('AA:BB:CC:DD:EE:FF', { * maxRetries: 10, * initialDelayMs: 1000, * maxDelayMs: 60000, * backoffMultiplier: 1.5 * }, { * onReconnect: (device) => console.log('Reconnected!', device.id), * onReconnectFailed: (deviceId, error) => console.log('Failed to reconnect', deviceId) * }); * * // Disable auto-reconnect * reconnectionManager.disableAutoReconnect('AA:BB:CC:DD:EE:FF'); */ class ReconnectionManager { _devices = new Map(); _globalCallbacks = {}; constructor(manager) { this._manager = manager; } /** * Set global callbacks for all reconnection events. * * @param callbacks Callback functions for reconnection events */ setGlobalCallbacks(callbacks) { this._globalCallbacks = callbacks; } /** * Enable automatic reconnection for a device. * * @param deviceId Device identifier * @param options Reconnection options (retries, delays, etc.) * @param callbacks Optional callbacks for this specific device */ enableAutoReconnect(deviceId, options, callbacks) { // If already enabled, disable first if (this._devices.has(deviceId)) { this.disableAutoReconnect(deviceId); } const reconnectionOptions = { maxRetries: options?.maxRetries ?? 5, initialDelayMs: options?.initialDelayMs ?? 1000, maxDelayMs: options?.maxDelayMs ?? 30000, backoffMultiplier: options?.backoffMultiplier ?? 2, connectionOptions: options?.connectionOptions }; const state = { deviceId, options: reconnectionOptions, isReconnecting: false, retryCount: 0, onReconnect: callbacks?.onReconnect, onReconnectFailed: callbacks?.onReconnectFailed, cancelled: false }; // Subscribe to disconnection events for this device state.disconnectSubscription = this._manager.onDeviceDisconnected(deviceId, (error, _device) => { // Only auto-reconnect on unexpected disconnections (error is not null) // If error is null, it was an intentional disconnect via cancelDeviceConnection if (error) { this._startReconnection(deviceId); } }); this._devices.set(deviceId, state); } /** * Disable automatic reconnection for a device. * * @param deviceId Device identifier * @returns True if auto-reconnect was disabled, false if it wasn't enabled */ disableAutoReconnect(deviceId) { const state = this._devices.get(deviceId); if (!state) { return false; } // Mark as cancelled to stop in-flight reconnection attempts state.cancelled = true; // Clear any pending reconnection attempt if (state.timeoutId) { clearTimeout(state.timeoutId); } // Remove disconnect subscription if (state.disconnectSubscription) { state.disconnectSubscription.remove(); } this._devices.delete(deviceId); return true; } /** * Disable auto-reconnect for all devices. */ disableAll() { const deviceIds = Array.from(this._devices.keys()); for (const deviceId of deviceIds) { this.disableAutoReconnect(deviceId); } } /** * Check if auto-reconnect is enabled for a device. * * @param deviceId Device identifier * @returns True if auto-reconnect is enabled */ isEnabled(deviceId) { return this._devices.has(deviceId); } /** * Check if a device is currently reconnecting. * * @param deviceId Device identifier * @returns True if reconnection is in progress */ isReconnecting(deviceId) { const state = this._devices.get(deviceId); return state?.isReconnecting ?? false; } /** * Get the current retry count for a device. * * @param deviceId Device identifier * @returns Current retry count, or -1 if auto-reconnect is not enabled */ getRetryCount(deviceId) { const state = this._devices.get(deviceId); return state?.retryCount ?? -1; } /** * Manually trigger a reconnection attempt. * Useful if you want to retry after all automatic retries have been exhausted. * * @param deviceId Device identifier * @returns Promise resolving to connected Device */ async manualReconnect(deviceId) { const state = this._devices.get(deviceId); if (!state) { throw new Error(`Auto-reconnect not enabled for device ${deviceId}`); } // Reset retry count for manual reconnect state.retryCount = 0; return this._attemptReconnection(deviceId); } /** * Start the reconnection process for a device */ _startReconnection(deviceId) { const state = this._devices.get(deviceId); if (!state || state.isReconnecting) { return; } state.isReconnecting = true; state.retryCount = 0; this._scheduleReconnection(deviceId, 0); } /** * Schedule a reconnection attempt with delay */ _scheduleReconnection(deviceId, delay) { const state = this._devices.get(deviceId); if (!state) { return; } state.timeoutId = setTimeout(async () => { try { await this._attemptReconnection(deviceId); } catch (_error) { // Error handling is done in _attemptReconnection } }, delay); } /** * Attempt to reconnect to a device */ async _attemptReconnection(deviceId) { const state = this._devices.get(deviceId); if (!state) { throw new Error(`Auto-reconnect not enabled for device ${deviceId}`); } // Check if cancelled before attempting if (state.cancelled) { throw new Error(`Auto-reconnect cancelled for device ${deviceId}`); } state.retryCount++; const maxRetries = state.options.maxRetries ?? 5; // Notify about reconnection attempt this._globalCallbacks.onReconnecting?.(deviceId, state.retryCount, maxRetries); try { const device = await this._manager.connectToDevice(deviceId, state.options.connectionOptions); // Check if cancelled or removed after await (race condition protection) const currentState = this._devices.get(deviceId); if (!currentState || currentState !== state || currentState.cancelled) { // State was disabled during connection - disconnect immediately await this._manager.cancelDeviceConnection(deviceId).catch(() => { // Ignore errors when cleaning up }); throw new Error(`Auto-reconnect cancelled for device ${deviceId} during connection`); } // Success! state.isReconnecting = false; state.retryCount = 0; // Notify callbacks state.onReconnect?.(device); this._globalCallbacks.onReconnect?.(device); return device; } catch (error) { // Check if cancelled after await const currentState = this._devices.get(deviceId); if (!currentState || currentState !== state || currentState.cancelled) { // Don't schedule retry or call callbacks if cancelled throw new Error(`Auto-reconnect cancelled for device ${deviceId}`); } if (state.retryCount >= maxRetries) { // Max retries exceeded state.isReconnecting = false; const bleError = error; // Notify callbacks state.onReconnectFailed?.(deviceId, bleError); this._globalCallbacks.onReconnectFailed?.(deviceId, bleError); throw bleError; } // Calculate delay with exponential backoff const initialDelay = state.options.initialDelayMs ?? 1000; const maxDelay = state.options.maxDelayMs ?? 30000; const multiplier = state.options.backoffMultiplier ?? 2; const delay = Math.min(initialDelay * Math.pow(multiplier, state.retryCount - 1), maxDelay); // Schedule next attempt this._scheduleReconnection(deviceId, delay); throw error; } } /** * Destroy the manager and disable all auto-reconnect. */ destroy() { this.disableAll(); this._globalCallbacks = {}; } } exports.ReconnectionManager = ReconnectionManager; //# sourceMappingURL=ReconnectionManager.js.map