@elshaer/homebridge-lg-thinq
Version:
A Homebridge plugin for controlling/monitoring LG ThinQ device via LG ThinQ platform.
260 lines • 10.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.API = void 0;
const constants = __importStar(require("./constants"));
const url_1 = require("url");
const Session_1 = require("./Session");
const Gateway_1 = require("./Gateway");
const request_1 = require("./request");
const Auth_1 = require("./Auth");
const errors_1 = require("../errors");
const crypto_1 = __importDefault(require("crypto"));
const axios_1 = __importDefault(require("axios"));
function resolveUrl(from, to) {
const url = new url_1.URL(to, from);
return url.href;
}
class API {
constructor(country = 'US', language = 'en-US') {
this.country = country;
this.language = language;
this.session = new Session_1.Session('', '', 0);
this.httpClient = request_1.requestClient;
this.logger = console;
}
async getRequest(uri) {
return await this.request('get', uri);
}
async postRequest(uri, data) {
return await this.request('post', uri, data);
}
async request(method, uri, data, retry = false) {
var _a, _b;
// eslint-disable-next-line max-len
const requestHeaders = (((_a = this._gateway) === null || _a === void 0 ? void 0 : _a.thinq1_url) && uri.startsWith(this._gateway.thinq1_url)) ? this.monitorHeaders : this.defaultHeaders;
const url = resolveUrl((_b = this._gateway) === null || _b === void 0 ? void 0 : _b.thinq2_url, uri);
return await this.httpClient.request({
method, url, data,
headers: requestHeaders,
}).then(res => res.data).catch(async (err) => {
if (err instanceof errors_1.TokenExpiredError && !retry) {
return await this.refreshNewToken().then(async () => {
return await this.request(method, uri, data, true);
}).catch((err) => {
this.logger.debug('refresh new token error: ', err);
return {};
});
}
else if (err instanceof errors_1.ManualProcessNeeded) {
this.logger.warn('Handling new term agreement... If you keep getting this message, ' + err.message);
await this.auth.handleNewTerm(this.session.accessToken)
.then(() => {
this.logger.warn('LG new term agreement is accepted.');
})
.catch(err => {
this.logger.debug(err);
});
if (!retry) {
// retry 1 times
return await this.request(method, uri, data, true);
}
else {
return {};
}
}
else {
if (axios_1.default.isAxiosError(err)) {
this.logger.debug('request error: ', err.response);
}
else if (!(err instanceof errors_1.NotConnectedError)) {
this.logger.debug('request error: ', err);
}
return {};
}
});
}
get monitorHeaders() {
var _a, _b;
const monitorHeaders = {
'Accept': 'application/json',
'x-thinq-application-key': 'wideq',
'x-thinq-security-key': 'nuts_securitykey',
};
if (typeof ((_a = this.session) === null || _a === void 0 ? void 0 : _a.accessToken) === 'string') {
monitorHeaders['x-thinq-token'] = (_b = this.session) === null || _b === void 0 ? void 0 : _b.accessToken;
}
if (this.jsessionId) {
monitorHeaders['x-thinq-jsessionId'] = this.jsessionId;
}
return monitorHeaders;
}
get defaultHeaders() {
function random_string(length) {
const result = [];
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result.push(characters.charAt(Math.floor(Math.random() * charactersLength)));
}
return result.join('');
}
const headers = {};
if (this.session.accessToken) {
headers['x-emp-token'] = this.session.accessToken;
}
if (this.userNumber) {
headers['x-user-no'] = this.userNumber;
}
headers['x-client-id'] = this.client_id || constants.API_CLIENT_ID;
return {
'x-api-key': constants.API_KEY,
'x-thinq-app-ver': '3.6.1200',
'x-thinq-app-type': 'NUTS',
'x-thinq-app-level': 'PRD',
'x-thinq-app-os': 'ANDROID',
'x-thinq-app-logintype': 'LGE',
'x-service-code': 'SVC202',
'x-country-code': this.country,
'x-language-code': this.language,
'x-service-phase': 'OP',
'x-origin': 'app-native',
'x-model-name': 'samsung/SM-G930L',
'x-os-version': 'AOS/7.1.2',
'x-app-version': 'LG ThinQ/3.6.12110',
'x-message-id': random_string(22),
'user-agent': 'okhttp/3.14.9',
...headers,
};
}
async getSingleDevice(device_id) {
return await this.getRequest('service/devices/' + device_id).then(data => data.result);
}
async getListDevices() {
const homes = await this.getListHomes();
const devices = [];
// get all devices in home
for (let i = 0; i < homes.length; i++) {
const resp = await this.getRequest('service/homes/' + homes[i].homeId);
devices.push(...resp.result.devices);
}
return devices;
}
async getListHomes() {
if (!this._homes) {
this._homes = await this.getRequest('service/homes').then(data => data.result.item);
}
return this._homes;
}
async sendCommandToDevice(device_id, values, command, ctrlKey = 'basicCtrl', ctrlPath = 'control-sync') {
return await this.postRequest('service/devices/' + device_id + '/' + ctrlPath, {
ctrlKey,
'command': command,
...values,
});
}
async sendMonitorCommand(deviceId, cmdOpt, workId) {
const data = {
cmd: 'Mon',
cmdOpt,
deviceId,
workId,
};
return await this.thinq1PostRequest('rti/rtiMon', data);
}
async getMonitorResult(device_id, work_id) {
return await this.thinq1PostRequest('rti/rtiResult', { workList: [{ deviceId: device_id, workId: work_id }] })
.then(data => {
if (!('workList' in data) || !('returnCode' in data.workList)) {
return null;
}
const workList = data.workList;
if (workList.returnCode !== '0000') {
throw new errors_1.MonitorError(data);
}
if (!('returnData' in workList)) {
return null;
}
return Buffer.from(workList.returnData, 'base64');
});
}
setRefreshToken(refreshToken) {
this.session = new Session_1.Session('', refreshToken, 0);
}
setUsernamePassword(username, password) {
this.username = username;
this.password = password;
}
async gateway() {
if (!this._gateway) {
const gateway = await request_1.requestClient.get(constants.GATEWAY_URL, { headers: this.defaultHeaders }).then(res => res.data.result);
this._gateway = new Gateway_1.Gateway(gateway);
}
return this._gateway;
}
async ready() {
var _a;
// get gateway first
const gateway = await this.gateway();
if (!this.auth) {
this.auth = new Auth_1.Auth(gateway);
this.auth.logger = this.logger;
}
if (!this.session.hasToken() && this.username && this.password) {
this.session = await this.auth.login(this.username, this.password);
await this.refreshNewToken(this.session);
}
if (!this.session.hasValidToken() && !!this.session.refreshToken) {
await this.refreshNewToken(this.session);
}
if (!this.jsessionId) {
// get new jsessionid
this.jsessionId = await this.auth.getJSessionId(this.session.accessToken);
}
if (!this.userNumber) {
this.userNumber = await this.auth.getUserNumber((_a = this.session) === null || _a === void 0 ? void 0 : _a.accessToken);
}
if (!this.client_id) {
const hash = crypto_1.default.createHash('sha256');
this.client_id = hash.update(this.userNumber + (new Date()).getTime()).digest('hex');
}
}
async refreshNewToken(session = null) {
session = session || this.session;
this.session = await this.auth.refreshNewToken(session);
this.jsessionId = await this.auth.getJSessionId(this.session.accessToken);
}
async thinq1PostRequest(endpoint, data) {
var _a;
return await this.postRequest(((_a = this._gateway) === null || _a === void 0 ? void 0 : _a.thinq1_url) + endpoint, {
lgedmRoot: data,
}).then(data => data.lgedmRoot);
}
}
exports.API = API;
//# sourceMappingURL=API.js.map