tsvesync
Version:
A TypeScript library for interacting with VeSync smart home devices
276 lines (275 loc) • 9.31 kB
JavaScript
;
/**
* 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;