homebridge-levoit-humidifiers
Version:
Homebridge plugin for Levoit Humidifiers
296 lines • 11.1 kB
TypeScript
import { Logger, PlatformConfig } from 'homebridge';
import DebugMode from '../debugMode';
import VeSyncFan from './VeSyncFan';
/**
* VeSync API bypass methods for device control.
* These methods are sent to the VeSync API to control device features.
*/
export declare enum BypassMethod {
STATUS = "getHumidifierStatus",
MODE = "setHumidityMode",
NIGHT_LIGHT_BRIGHTNESS = "setNightLightBrightness",
DISPLAY = "setDisplay",
SWITCH = "setSwitch",
HUMIDITY = "setTargetHumidity",
MIST_LEVEL = "setVirtualLevel",
LEVEL = "setLevel",
LIGHT_STATUS = "setLightStatus",
DRYING_MODE = "setDryingMode"
}
interface DeviceResult {
humidity?: number;
targetHumidity?: number;
screenSwitch?: boolean;
workMode?: string;
powerSwitch?: number;
autoStopState?: boolean;
virtualLevel?: number;
configuration?: {
auto_target_humidity?: number;
};
display?: boolean;
mode?: string;
enabled?: boolean;
automatic_stop_reach_target?: boolean;
mist_virtual_level?: number;
warm_level?: number;
warm_enabled?: boolean;
night_light_brightness?: number;
rgbNightLight?: {
brightness?: number;
action?: string;
blue?: number;
green?: number;
red?: number;
colorMode?: string;
speed?: number;
colorSliderLocation?: number;
};
}
interface DeviceInfoResponse {
result?: {
result?: DeviceResult;
};
msg?: string;
}
/**
* VeSync API client for authenticating and communicating with VeSync devices.
*
* Features:
* - Two-step authentication with session persistence
* - Automatic cross-region detection and switching
* - Session token caching to disk for faster re-authentication
* - Automatic token refresh on 401 errors
* - Login backoff to prevent API abuse
* - Support for US and EU API endpoints
*
* The authentication flow:
* 1. Step 1: authByPWDOrOTM - Authenticates with email/password, returns authorizeCode
* 2. Step 2: loginByAuthorizeCode4Vesync - Exchanges authorizeCode for session token
* 3. If cross-region detected, retries step 2 with correct region
*/
export default class VeSync {
private readonly email;
private readonly password;
readonly config: PlatformConfig;
readonly debugMode: DebugMode;
readonly log: Logger;
private api?;
private accountId?;
private token?;
/**
* Dynamic baseURL; starts from config/country and may flip on cross-region detection.
* Automatically switches between US and EU hosts based on account region.
*/
private baseURL;
/**
* Track account country once known from login response.
* Used to determine correct API host.
*/
private countryCode;
/**
* Device region (e.g., 'US', 'EU') from login response.
*/
private region?;
private readonly VERSION;
private readonly FULL_VERSION;
private readonly AGENT;
private readonly TIMEZONE;
private readonly OS;
private readonly BRAND;
private readonly LANG;
/**
* Terminal/device identifier that VeSync expects to remain stable across sessions.
* Generated once per instance and used for all API calls.
*/
private readonly terminalId;
/**
* Application ID used for authentication requests.
* Randomly generated per instance.
*/
private readonly appID;
/**
* Simple login backoff to prevent hammering the API on repeated failures.
* Starts at 10 seconds, doubles on each failure, caps at 5 minutes.
*/
private lastLoginAttempt;
private loginBackoffMs;
/**
* Session persistence file path.
* Stores authentication token and account info for faster re-authentication.
*/
private readonly sessionFilePath?;
/**
* Maximum age for session tokens (25 days).
* Tokens older than this are considered invalid even if JWT doesn't specify expiration.
*/
private readonly TOKEN_MAX_AGE_MS;
private readonly BYPASS_HEADER_UA;
private readonly AUTH_APP_VERSION;
private readonly AUTH_CLIENT_VERSION;
private readonly AUTH_CLIENT_INFO;
private readonly AUTH_OS_INFO;
constructor(email: string, password: string, config: PlatformConfig, debugMode: DebugMode, log: Logger, sessionPath?: string);
/**
* Gets axios options for device API calls.
* @returns Axios configuration with baseURL and timeout
*/
private AXIOS_OPTIONS;
/**
* Gets axios options for authentication API calls.
* @param host - Optional host override (defaults to baseURL)
* @returns Axios configuration with authentication headers
*/
private AUTH_AXIOS_OPTIONS;
/**
* Generates detail body for device API requests.
* Contains app version, device info, and trace ID.
* @returns Detail body object
*/
private generateDetailBody;
/**
* Generates base body for API requests.
* @param includeAuth - Whether to include accountID and token
* @returns Base body object with language, timezone, and optionally auth
*/
private generateBody;
/**
* Generates V2 bypass body for device control commands.
* @param fan - The device to send command to
* @param method - The bypass method to execute
* @param data - Command-specific data payload
* @returns V2 bypass body object
*/
private generateV2Body;
/**
* Generates a unique trace ID for authentication requests.
* Format: APP{appID}{timestamp}
* @returns Trace ID string
*/
private generateAuthTraceId;
/**
* Loads persisted session from disk if available and valid.
* Validates token expiration and account match before returning.
*
* @returns Session data if valid, null otherwise
*/
private loadSessionFromDisk;
/**
* Saves current session to disk for faster re-authentication.
* Includes token, account ID, country code, and expiration info.
*/
private saveSessionToDisk;
/**
* Checks if the current token is still valid.
* Validates JWT expiration if present, or checks token age against max age.
*
* @returns true if token is valid, false if expired or missing
*/
private isTokenValid;
/**
* Builds and configures the axios API client with authentication headers.
* Sets up automatic token refresh on 401 errors.
*
* @throws Error if token or accountId is missing
*/
private buildApiClient;
/**
* Handles device offline error response.
* Checks if the response indicates device is offline and handles accordingly.
*
* @param responseMsg - The message from the API response (may be undefined)
* @param returnValue - Value to return if showOffWhenDisconnected is enabled
* @returns The returnValue if showOffWhenDisconnected is enabled and device is offline
* @throws Error if showOffWhenDisconnected is disabled and device is offline
* @returns undefined if device is not offline (caller should continue normal processing)
*/
private handleDeviceOffline;
/**
* Checks if the API response indicates quota exceeded error.
* Logs a warning and returns true if quota is exceeded.
*
* @param responseCode - The error code from the API response
* @param responseMsg - The error message from the API response
* @returns true if quota is exceeded, false otherwise
*/
private handleQuotaExceeded;
/**
* Ensures the authentication token is valid before making API calls.
* Proactively checks token expiration and refreshes if needed.
*
* @throws Error if token refresh fails or API client is unavailable
*/
private ensureValidToken;
/**
* Sends a control command to a device.
* Thread-safe: Uses AsyncLock to prevent concurrent API calls.
* Automatically refreshes token if expired before making the request.
*
* @param fan - The device to send command to
* @param method - The bypass method to execute
* @param body - Command-specific data payload
* @returns true if command succeeded (code === 0), false otherwise
* @throws Error if not logged in or device is unreachable (unless showOffWhenDisconnected is enabled)
*/
sendCommand(fan: VeSyncFan, method: BypassMethod, body?: {}): Promise<boolean>;
/**
* Gets current device state/info from the VeSync API.
* Thread-safe: Uses AsyncLock to prevent concurrent API calls.
* Automatically refreshes token if expired before making the request.
*
* @param fan - The device to get info for
* @returns Device info response, or null if device is offline and showOffWhenDisconnected is enabled
* @throws Error if not logged in or device is unreachable (unless showOffWhenDisconnected is enabled)
*/
getDeviceInfo(fan: VeSyncFan): Promise<DeviceInfoResponse | null>;
/**
* Starts an authentication session.
* First attempts to reuse a persisted session from disk.
* If no valid session exists, performs a fresh login.
*
* @returns true if session started successfully, false otherwise
*/
startSession(): Promise<boolean>;
/**
* Performs a two-step login flow with cross-region detection.
* Step 1: Authenticates with email/password to get authorizeCode
* Step 2: Exchanges authorizeCode for session token
* If cross-region detected, automatically retries with correct region.
*
* Implements login backoff to prevent API abuse on failures.
*
* @returns true if login successful, false otherwise
* @throws Error if email/password are missing
*/
private login;
/**
* Step 1 of authentication: Authenticates with email/password.
* Returns an authorizeCode that is used in step 2 to get the session token.
* Falls back to accountapi.vesync.com if smartapi fails.
*
* @param userCountryCode - Country code for the authentication request
* @returns Object with authorizeCode and optional bizToken
* @throws Error if authentication fails
*/
private authByPWDOrOTM;
/**
* Step 2 of authentication: Exchanges authorizeCode for session token.
* May return a cross-region response indicating the account is in a different region.
*
* @param opts - Login options including country code, host, authorizeCode, etc.
* @returns Login response with token and account info, or undefined on network error
*/
private loginByAuthorizeCode4Vesync;
/**
* Gets all supported humidifier devices from the VeSync account.
* Filters devices to only include supported models (wifi-air type).
* Thread-safe: Uses AsyncLock to prevent concurrent API calls.
*
* Token expiration is handled automatically by the axios interceptor.
*
* @returns Array of VeSyncFan instances for supported devices
*/
getDevices(): Promise<VeSyncFan[]>;
}
export {};
//# sourceMappingURL=VeSync.d.ts.map