UNPKG

tsvesync

Version:

A TypeScript library for interacting with VeSync smart home devices

276 lines (275 loc) 9.31 kB
"use strict"; /** * Helper functions for VeSync API */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Helpers = exports.BYPASS_HEADER_UA = exports.MOBILE_ID = exports.DEFAULT_REGION = exports.DEFAULT_TZ = exports.USER_TYPE = exports.PHONE_OS = exports.PHONE_BRAND = exports.APP_VERSION = exports.API_TIMEOUT = exports.API_RATE_LIMIT = void 0; exports.getApiBaseUrl = getApiBaseUrl; exports.setApiBaseUrl = setApiBaseUrl; const axios_1 = __importDefault(require("axios")); const crypto_1 = __importDefault(require("crypto")); const logger_1 = require("./logger"); // API configuration - Always use US endpoint let _apiBaseUrl = 'https://smartapi.vesync.com'; function getApiBaseUrl() { return _apiBaseUrl; } function setApiBaseUrl(url) { _apiBaseUrl = url; } exports.API_RATE_LIMIT = 30; exports.API_TIMEOUT = 15000; exports.APP_VERSION = '2.8.6'; exports.PHONE_BRAND = 'SM N9005'; exports.PHONE_OS = 'Android'; exports.USER_TYPE = '1'; exports.DEFAULT_TZ = 'America/New_York'; exports.DEFAULT_REGION = 'US'; exports.MOBILE_ID = '1234567890123456'; exports.BYPASS_HEADER_UA = 'okhttp/3.12.1'; class Helpers { /** * Calculate MD5 hash */ static hashPassword(text) { return crypto_1.default.createHash('md5').update(text).digest('hex'); } /** * Build header for legacy api GET requests */ static reqHeaders(manager) { if (!manager.accountId || !manager.token) { throw new Error('Manager accountId and token must be set'); } return { 'Content-Type': 'application/json; charset=UTF-8', 'User-Agent': exports.BYPASS_HEADER_UA, 'accept-language': 'en', 'accountId': manager.accountId, 'appVersion': exports.APP_VERSION, 'content-type': 'application/json', 'tk': manager.token, 'tz': manager.timeZone }; } /** * Build header for api requests on 'bypass' endpoint */ static reqHeaderBypass() { return { 'Content-Type': 'application/json; charset=UTF-8', 'User-Agent': exports.BYPASS_HEADER_UA }; } /** * Return universal keys for body of api requests */ static reqBodyBase(manager) { return { 'timeZone': manager.timeZone, 'acceptLanguage': 'en' }; } /** * Keys for authenticating api requests */ static reqBodyAuth(manager) { if (!manager.accountId || !manager.token) { throw new Error('Manager accountId and token must be set'); } return { 'accountID': manager.accountId, 'token': manager.token }; } /** * Detail keys for api requests */ static reqBodyDetails() { return { 'appVersion': exports.APP_VERSION, 'phoneBrand': exports.PHONE_BRAND, 'phoneOS': exports.PHONE_OS, 'traceId': Date.now().toString() }; } /** * Builder for body of api requests */ static reqBody(manager, type) { const body = { ...this.reqBodyBase(manager) }; if (type === 'login') { return { ...body, ...this.reqBodyDetails(), email: manager.username, password: this.hashPassword(manager.password), devToken: '', userType: exports.USER_TYPE, method: 'login' }; } const authBody = { ...body, ...this.reqBodyAuth(manager) }; if (type === 'devicestatus') { return authBody; } const fullBody = { ...authBody, ...this.reqBodyDetails() }; switch (type) { case 'devicelist': return { ...fullBody, method: 'devices', pageNo: '1', pageSize: '100' }; case 'devicedetail': return { ...fullBody, method: 'devicedetail', mobileId: exports.MOBILE_ID }; case 'bypass': return { ...fullBody, method: 'bypass' }; case 'bypassV2': return { ...fullBody, deviceRegion: exports.DEFAULT_REGION, method: 'bypassV2' }; case 'bypass_config': return { ...fullBody, method: 'firmwareUpdateInfo' }; default: return fullBody; } } /** * Call VeSync API */ static async callApi(endpoint, method, data = null, headers = {}, manager) { try { // Ensure API base URL is properly set if (!_apiBaseUrl || _apiBaseUrl === 'undefined') { logger_1.logger.error('API base URL is not properly configured. Setting to default US endpoint...'); setApiBaseUrl('https://smartapi.vesync.com'); } const url = _apiBaseUrl + endpoint; logger_1.logger.debug(`Making API call to: ${url}`); const response = await (0, axios_1.default)({ method, url, data, headers, timeout: exports.API_TIMEOUT }); return [response.data, response.status]; } catch (error) { if (error.response) { const responseData = error.response.data; // Check for token expiration if ((responseData === null || responseData === void 0 ? void 0 : responseData.code) === 4001004 || (responseData === null || responseData === void 0 ? void 0 : responseData.msg) === "token expired") { logger_1.logger.debug('Token expired, attempting to re-login...'); // Re-login if (await manager.login()) { // Retry the original request logger_1.logger.debug('Re-login successful, retrying original request...'); return await this.callApi(endpoint, method, data, headers, manager); } } // Log specific error details for debugging logger_1.logger.error('API call failed with response:', { status: error.response.status, code: responseData === null || responseData === void 0 ? void 0 : responseData.code, message: responseData === null || responseData === void 0 ? void 0 : responseData.msg, url: _apiBaseUrl + endpoint }); return [responseData, error.response.status]; } logger_1.logger.error('API call failed:', { code: error.code, message: error.message, url: _apiBaseUrl + endpoint }); return [null, 0]; } } /** * Calculate hex value from energy response */ static calculateHex(hexStr) { if (!hexStr || !hexStr.includes(':')) { return '0'; } const [prefix, value] = hexStr.split(':'); if (!prefix || !value) { return '0'; } try { const decoded = Buffer.from(value, 'hex'); return decoded.readFloatBE(0).toString(); } catch (error) { logger_1.logger.debug('Error decoding hex value:', error); return '0'; } } /** * Build energy dictionary from API response */ static buildEnergyDict(result) { if (!result) { return {}; } return { energy_consumption_of_today: result.energyConsumptionOfToday || 0, cost_per_kwh: result.costPerKWH || 0, max_energy: result.maxEnergy || 0, total_energy: result.totalEnergy || 0, data: result.data || [], energy_consumption: result.energy || 0, start_time: result.startTime || '', end_time: result.endTime || '' }; } /** * Build configuration dictionary from API response */ static buildConfigDict(result) { if (!result) { return {}; } return { configModule: result.configModule || '', firmwareVersion: result.currentFirmVersion || '', deviceRegion: result.deviceRegion || '', debugMode: result.debugMode || false, deviceTimezone: result.deviceTimezone || exports.DEFAULT_TZ, ...result }; } /** * Calculate MD5 hash */ static md5(text) { return crypto_1.default.createHash('md5').update(text).digest('hex'); } } exports.Helpers = Helpers; Helpers.shouldRedact = true;