homebridge-ecobee-status
Version:
Homebridge plugin to control Ecobee thermostat Home/Away/Sleep status through HomeKit security system interface
132 lines • 6.03 kB
JavaScript
"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