@s1rd4v3/homebridge-tuya-web
Version:
Homebridge plugin for use with Tuya devices (uses Home Assistant Tuya Web API)
448 lines (369 loc) • 12.8 kB
JavaScript
;
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["exports", "axios", "querystring", "./errors"], factory);
} else if (typeof exports !== "undefined") {
factory(exports, require("axios"), require("querystring"), require("./errors"));
} else {
var mod = {
exports: {}
};
factory(mod.exports, global.axios, global.querystring, global.errors);
global.undefined = mod.exports;
}
})(void 0, function (exports, _axios, _querystring, _errors) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TuyaWebApi = exports.TuyaPlatforms = exports.TuyaDeviceTypes = undefined;
var _axios2 = _interopRequireDefault(_axios);
var querystring = _interopRequireWildcard(_querystring);
function _getRequireWildcardCache() {
if (typeof WeakMap !== "function") return null;
var cache = new WeakMap();
_getRequireWildcardCache = function () {
return cache;
};
return cache;
}
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
}
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
return {
default: obj
};
}
var cache = _getRequireWildcardCache();
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
var TuyaDeviceTypes = exports.TuyaDeviceTypes = ['light', 'fan', 'dimmer', 'switch', 'outlet', 'scene', 'cover'];
var TuyaPlatforms = exports.TuyaPlatforms = ['tuya', 'smart_life', 'jinvoo_smart'];
class Session {
constructor(_accessToken, _refreshToken, expiresIn, _areaCode) {
this._accessToken = _accessToken;
this._refreshToken = _refreshToken;
this.expiresIn = expiresIn;
this._areaCode = _areaCode;
this.areaCode = _areaCode;
this.resetToken(_accessToken, _refreshToken, expiresIn);
}
get accessToken() {
return this._accessToken;
}
get areaBaseUrl() {
return this._areaBaseUrl;
}
get refreshToken() {
return this._refreshToken;
}
set areaCode(newAreaCode) {
var areaCodeLookup = {
'AY': 'https://px1.tuyacn.com',
'EU': 'https://px1.tuyaeu.com',
'US': 'https://px1.tuyaus.com'
};
this._areaCode = newAreaCode;
this._areaBaseUrl = areaCodeLookup[newAreaCode] || areaCodeLookup['US'];
}
resetToken(accessToken, refreshToken, expiresIn) {
this._accessToken = accessToken;
this._refreshToken = refreshToken;
this.expiresOn = Session.getCurrentEpoch() + expiresIn - 100; // subtract 100 ticks to expire token before it actually does
}
hasToken() {
return !!this._accessToken;
}
isTokenExpired() {
return this.expiresOn < Session.getCurrentEpoch();
}
hasValidToken() {
return this.hasToken() && !this.isTokenExpired();
}
static getCurrentEpoch() {
return Math.round(new Date().getTime() / 1000);
}
}
class TuyaWebApi {
constructor(username, password, countryCode) {
var tuyaPlatform = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'tuya';
var log = arguments.length > 4 ? arguments[4] : undefined;
this.username = username;
this.password = password;
this.countryCode = countryCode;
this.tuyaPlatform = tuyaPlatform;
this.log = log;
this.authBaseUrl = 'https://px1.tuyaeu.com';
}
getAllDeviceStates() {
var _this = this;
return _asyncToGenerator(function* () {
return _this.discoverDevices();
})();
}
discoverDevices() {
var _this2 = this;
return _asyncToGenerator(function* () {
var _a;
if (!((_a = _this2.session) === null || _a === void 0 ? void 0 : _a.hasValidToken())) {
throw new Error('No valid token');
}
var {
data
} = yield _this2.sendRequest('/homeassistant/skill', {
header: {
name: 'Discovery',
namespace: 'discovery',
payloadVersion: 1
},
payload: {
accessToken: _this2.session.accessToken
}
}, 'GET');
if (data.header && data.header.code === 'SUCCESS') {
return data.payload.devices;
} else {
if (data.header && data.header.code === 'FrequentlyInvoke') {
throw new _errors.RatelimitError('Requesting too quickly.', data.header.msg);
} else {
throw new Error("No valid response from API: ".concat(JSON.stringify(data)));
}
}
})();
}
getDeviceState(deviceId) {
var _this3 = this;
return _asyncToGenerator(function* () {
var _a;
if (!((_a = _this3.session) === null || _a === void 0 ? void 0 : _a.hasValidToken())) {
throw new Error('No valid token');
}
var {
data
} = yield _this3.sendRequest('/homeassistant/skill', {
header: {
name: 'QueryDevice',
namespace: 'query',
payloadVersion: 1
},
payload: {
accessToken: _this3.session.accessToken,
devId: deviceId,
value: 1
}
}, 'GET');
if (data.header && data.header.code === 'SUCCESS') {
return data.payload.data;
} else {
if (data.header && data.header.code === 'FrequentlyInvoke') {
throw new _errors.RatelimitError('Requesting too quickly.', data.header.msg);
} else {
throw new Error("No valid response from API: ".concat(JSON.stringify(data)));
}
}
})();
}
setDeviceState(deviceId, method, payload) {
var _this4 = this;
return _asyncToGenerator(function* () {
var _a, _b;
if (!((_a = _this4.session) === null || _a === void 0 ? void 0 : _a.hasValidToken())) {
throw new Error('No valid token');
}
var {
data
} = yield _this4.sendRequest('/homeassistant/skill', {
header: {
name: method,
namespace: 'control',
payloadVersion: 1
},
payload: _objectSpread(_objectSpread({}, payload), {}, {
accessToken: (_b = _this4.session) === null || _b === void 0 ? void 0 : _b.accessToken,
devId: deviceId
})
}, 'POST');
if (data.header && data.header.code === 'SUCCESS') {
return;
} else if (data.header && data.header.code === 'FrequentlyInvoke') {
throw new _errors.RatelimitError('Requesting too quickly.', data.header.msg);
} else {
throw new Error("Invalid payload in response: ".concat(JSON.stringify(data)));
}
})();
}
getOrRefreshToken() {
var _this5 = this;
return _asyncToGenerator(function* () {
var _a, _b, _c;
if (!((_a = _this5.session) === null || _a === void 0 ? void 0 : _a.hasToken())) {
(_b = _this5.log) === null || _b === void 0 ? void 0 : _b.debug('Requesting new token'); // No token, lets get a token from the Tuya Web API
if (!_this5.username) {
throw new _errors.AuthenticationError('No username configured');
}
if (!_this5.password) {
throw new _errors.AuthenticationError('No password configured');
}
if (!_this5.countryCode) {
throw new _errors.AuthenticationError('No country code configured');
}
var form = {
userName: _this5.username,
password: _this5.password,
countryCode: _this5.countryCode,
bizType: _this5.tuyaPlatform,
from: 'tuya'
};
var formData = querystring.stringify(form);
var contentLength = formData.length;
var {
data
} = yield (0, _axios2.default)({
headers: {
'Content-Length': contentLength,
'Content-Type': 'application/x-www-form-urlencoded'
},
url: '/homeassistant/auth.do',
baseURL: _this5.authBaseUrl,
data: formData,
method: 'POST'
});
if (data.responseStatus === 'error') {
throw new _errors.AuthenticationError(data.errorMsg);
} else {
_this5.session = new Session(data.access_token, data.refresh_token, data.expires_in, data.access_token.substr(0, 2));
return _this5.session;
}
} else {
(_c = _this5.log) === null || _c === void 0 ? void 0 : _c.debug('Refreshing token');
if (_this5.session.isTokenExpired()) {
// Refresh token
var {
data: _data
} = yield _this5.sendRequest('/homeassistant/access.do?grant_type=refresh_token&refresh_token=' + _this5.session.refreshToken, {}, 'GET'); // Received token
_this5.session.resetToken(_data.access_token, _data.refresh_token, _data.expires_in);
return _this5.session;
}
}
})();
}
/*
* --------------------------------------
* HTTP methods
*/
sendRequest(url, data, method) {
var _this6 = this;
return _asyncToGenerator(function* () {
var _a, _b;
(_a = _this6.log) === null || _a === void 0 ? void 0 : _a.debug('Sending HTTP %s request to %s - Header: %s.', method, url, JSON.stringify(data.header));
var response = yield (0, _axios2.default)({
baseURL: (_b = _this6.session) === null || _b === void 0 ? void 0 : _b.areaBaseUrl,
url,
data,
method
});
return {
data: response.data
};
})();
}
}
exports.TuyaWebApi = TuyaWebApi;
});