UNPKG

homebridge-ecobee-status

Version:

Homebridge plugin to control Ecobee thermostat Home/Away/Sleep status through HomeKit security system interface

132 lines 6.03 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthTokenManager = void 0; const moment_1 = __importDefault(require("moment")); const axios_1 = __importDefault(require("axios")); const querystring_1 = __importDefault(require("querystring")); const config_1 = require("./config"); const network_retry_1 = require("./network-retry"); class AuthTokenManager { constructor(platform) { this.platform = platform; this.ECOBEE_API_KEY = 'LvHbdQIXI5zoGoZW2uyWk2Ejfb1vtQWq'; this.TOKEN_REFRESH_BUFFER = 300; // Refresh 5 minutes before expiration this.authToken = ''; this.refreshToken = ''; this.expiration = (0, moment_1.default)(); this.refreshInProgress = false; this.lastRefreshAttempt = (0, moment_1.default)(0); this.refreshToken = platform.config.refreshToken; this.networkRetry = new network_retry_1.NetworkRetry({ totalWindowSeconds: this.TOKEN_REFRESH_BUFFER - 30, // Leave 30s buffer for overhead maxAttempts: 8, // Try up to 8 times over the window initialDelay: 15000, // Start with 15 seconds maxDelay: 60000, // Cap at 1 minute between retries backoffFactor: 2, // Double the delay each attempt }); } static configureForPlatform(platform) { if (!AuthTokenManager.instance) { AuthTokenManager.instance = new AuthTokenManager(platform); } } static getInstance() { return AuthTokenManager.instance; } isExpired() { return this.authToken === '' || (0, moment_1.default)().add(this.TOKEN_REFRESH_BUFFER, 'seconds').isAfter(this.expiration); } clearBackgroundRefresh() { if (this.backgroundRefreshTimeout) { clearTimeout(this.backgroundRefreshTimeout); this.backgroundRefreshTimeout = undefined; } } scheduleBackgroundRefresh(delayMs) { this.clearBackgroundRefresh(); this.backgroundRefreshTimeout = setTimeout(() => { this.renewAuthToken() .catch(error => { // Only log if it's not a known network error if (!this.networkRetry.isRetryableNetworkError(error)) { this.platform.log.error('Background token refresh failed:', error); } }); }, delayMs); } async renewAuthToken() { var _a; // Prevent multiple simultaneous refresh attempts if (this.refreshInProgress) { this.platform.log.debug('Token refresh already in progress, waiting...'); while (this.refreshInProgress) { await new Promise(resolve => setTimeout(resolve, 100)); } return this.authToken; } // Prevent too frequent refresh attempts const timeSinceLastAttempt = (0, moment_1.default)().diff(this.lastRefreshAttempt, 'seconds'); if (timeSinceLastAttempt < 30) { this.platform.log.debug(`Skipping refresh, last attempt was ${timeSinceLastAttempt}s ago`); return this.authToken; } this.refreshInProgress = true; this.lastRefreshAttempt = (0, moment_1.default)(); this.clearBackgroundRefresh(); try { if (!this.refreshToken) { throw new Error('No refresh token in config file'); } const oldRefreshToken = this.refreshToken; const authData = await this.networkRetry.execute(async () => { const response = await axios_1.default.post('https://api.ecobee.com/token', querystring_1.default.stringify({ grant_type: 'refresh_token', code: oldRefreshToken, client_id: this.ECOBEE_API_KEY, })); return response.data; }, this.platform.log, 'Token refresh'); const loadedAuthToken = authData.access_token; const loadedExpiresIn = authData.expires_in; const loadedUpdatedRefreshToken = (_a = authData.refresh_token) !== null && _a !== void 0 ? _a : oldRefreshToken; // Validate the received tokens if (!loadedAuthToken || !loadedExpiresIn) { throw new Error('Invalid token data received from Ecobee API'); } this.authToken = loadedAuthToken; this.refreshToken = loadedUpdatedRefreshToken; this.expiration = (0, moment_1.default)().add(loadedExpiresIn, 'seconds'); // Schedule next refresh before token expires const nextRefreshIn = (loadedExpiresIn - this.TOKEN_REFRESH_BUFFER) * 1000; this.scheduleBackgroundRefresh(nextRefreshIn); // Update config file with new refresh token if (oldRefreshToken !== loadedUpdatedRefreshToken) { const updated = (0, config_1.updateHomebridgeConfig)(this.platform.api, (currentConfig) => { return currentConfig.replace(oldRefreshToken, loadedUpdatedRefreshToken); }); if (updated) { this.platform.log.debug('Updated refresh token in config'); } } return loadedAuthToken; } catch (error) { // Only log detailed error if it's not a known network error if (!this.networkRetry.isRetryableNetworkError(error)) { this.platform.log.warn('Error refreshing token:', error); } // Schedule a retry in the background this.scheduleBackgroundRefresh(30000); // 30 seconds return undefined; } finally { this.refreshInProgress = false; } } } exports.AuthTokenManager = AuthTokenManager; //# sourceMappingURL=auth-token-refresh.js.map