mirror of
https://github.com/skydiver/ewelink-api.git
synced 2025-12-21 13:23:05 +01:00
Release v3.0.0 (#85)
* updated dependencies * code linting * added new app id & app secret * cleanup requests payloads * remove unused function * update test cases * enabled firmware tests * refactor getDevice to use right api endpoint * error messages improvements * error messages improvements * error messages improvements * error messages improvements * error messages improvements * payload cleanup * refactor setDevicePowerState to use right api endpoint * update test exepectation * removed deprecated class * updated tests to reflect new error codes * error messages improvements * refactoring project structure: devices methods refactoring project sturcture * refactoring project structure: firmware methods * refactoring project structure: temperature/humidity * refactoring project structure: credentials methods * refactoring project structure: power usage methods * refactoring project structure: power state methods * refactoring project structure: websocket methods * removed deprecated login method from docs * refactoring project structure: power usage methods * refactoring project structure: zeroconf classes * refactoring project structure: websocket classes * refactoring project structure: zeroconf classes * refactor and cleanup * refactoring project structure: firmware methods * moved parsers to own directory * update tests with methods renames * export missing temperature/humidity methods * removed unused package * refactor and cleanup * fix test expectation * refactoring project structure: moved data files * refactoring project structure: moved data files * refactoring project structure: moved helpers files * refactoring project structure: moved helpers files * refactoring project structure: moved payload files * refactor and cleanup * refactor getDevicePowerState * setDevicePowerState returns channel * convert error 400 to 404 for clarity * updated test cases * remove console.log * cache path for zeroconf cache files * installed nock * using nock to simulate server requests during testing * moved credentials file to config folder * update request url when using nock * refactor nock helper file * move cooldown delay to setupTests file * updating testing instructions * restored delete code block * fix wrong error code * accept phone number to login to ewelink * added test cases for initialize main class * improvements on class initialization parameters * allow login using phone number * rename test file * updated test case * fixed regression bug * Release v3.0.0 - use node-fetch (#87) * replaced deprecated request library with node-fetch * refactor: moved makeRequest to own mixin file * refactor to use node-fetch * fixes * update config * created helper method * constant rename * ignore files from final package * version bump
This commit is contained in:
9
.npmignore
Normal file
9
.npmignore
Normal file
@@ -0,0 +1,9 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
docs/
|
||||
test/
|
||||
.editorconfig
|
||||
.eslintrc
|
||||
arp-table.json
|
||||
demo.js
|
||||
devices-cache.json
|
||||
@@ -1,30 +0,0 @@
|
||||
const WebSocket = require('../WebSocket');
|
||||
const payloads = require('../../lib/payloads');
|
||||
const { _get } = require('../../lib/helpers');
|
||||
|
||||
class ChangeState extends WebSocket {
|
||||
static async set({ apiUrl, at, apiKey, deviceId, params, state }) {
|
||||
const payloadLogin = payloads.wssLoginPayload({ at, apiKey });
|
||||
|
||||
const payloadUpdate = payloads.wssUpdatePayload({
|
||||
apiKey,
|
||||
deviceId,
|
||||
params,
|
||||
});
|
||||
|
||||
const response = await this.WebSocketRequest(apiUrl, [
|
||||
payloadLogin,
|
||||
payloadUpdate,
|
||||
]);
|
||||
|
||||
const error = _get(response[1], 'error', false);
|
||||
|
||||
if (error === 403) {
|
||||
return { error, msg: response[1].reason };
|
||||
}
|
||||
|
||||
return { status: 'ok', state };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChangeState;
|
||||
@@ -1,7 +0,0 @@
|
||||
const ChangeState = require('./ChangeState');
|
||||
const ChangeStateZeroconf = require('./ChangeStateZeroconf');
|
||||
|
||||
module.exports = {
|
||||
ChangeState,
|
||||
ChangeStateZeroconf,
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
class CurrentMonth {
|
||||
/**
|
||||
* Return daily power usage
|
||||
*
|
||||
* @param hundredDaysKwhData
|
||||
*
|
||||
* @returns {{daily: *, monthly: *}}
|
||||
*/
|
||||
static parse({ hundredDaysKwhData }) {
|
||||
const today = new Date();
|
||||
const days = today.getDate();
|
||||
|
||||
let monthlyUsage = 0;
|
||||
const dailyUsage = [];
|
||||
|
||||
for (let day = 0; day < days; day += 1) {
|
||||
const s = hundredDaysKwhData.substr(6 * day, 2);
|
||||
const c = hundredDaysKwhData.substr(6 * day + 2, 2);
|
||||
const f = hundredDaysKwhData.substr(6 * day + 4, 2);
|
||||
const h = parseInt(s, 16);
|
||||
const y = parseInt(c, 16);
|
||||
const I = parseInt(f, 16);
|
||||
const E = parseFloat(`${h}.${y}${I}`);
|
||||
|
||||
dailyUsage.push({
|
||||
day: days - day,
|
||||
usage: E,
|
||||
});
|
||||
|
||||
monthlyUsage += E;
|
||||
}
|
||||
|
||||
return {
|
||||
monthly: monthlyUsage,
|
||||
daily: dailyUsage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CurrentMonth;
|
||||
@@ -1,7 +0,0 @@
|
||||
const DeviceRaw = require('./DeviceRaw');
|
||||
const CurrentMonth = require('./CurrentMonth');
|
||||
|
||||
module.exports = {
|
||||
DeviceRaw,
|
||||
CurrentMonth,
|
||||
};
|
||||
@@ -22,6 +22,5 @@
|
||||
* [getRegion](available-methods/getregion.md)
|
||||
* [getFirmwareVersion](available-methods/getfirmwareversion.md)
|
||||
* [saveDevicesCache](available-methods/savedevicescache.md)
|
||||
* [login](available-methods/login.md) <sup>_*deprecated_</sup>
|
||||
* [Zeroconf (LAN mode)](zeroconf.md)
|
||||
* [Testing](testing.md)
|
||||
@@ -17,7 +17,6 @@ Here is the list of available methods.
|
||||
* [getRegion](getregion.md)
|
||||
* [getFirmwareVersion](getfirmwareversion.md)
|
||||
* [saveDevicesCache](savedevicescache.md)
|
||||
* [login](login.md) <sup>_*deprecated_</sup>
|
||||
|
||||
Remember to instantiate class before usage.
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ Query for specified device power status.
|
||||
```js
|
||||
{
|
||||
status: 'ok',
|
||||
state: 'off'
|
||||
state: 'off',
|
||||
channel: 1
|
||||
}
|
||||
```
|
||||
@@ -1,20 +0,0 @@
|
||||
# Login
|
||||
|
||||
> DEPRECATED: use [getCredentials](available-methods/getcredentials.md) method instead
|
||||
|
||||
Login into eWeLink API and get auth credentials.
|
||||
|
||||
This method is useful on serverless context, where you need to obtain auth credentials to make individual requests.
|
||||
|
||||
|
||||
### Usage
|
||||
```
|
||||
const auth = await connection.login();
|
||||
|
||||
console.log('access token: ', auth.at);
|
||||
console.log('api key: ', auth.user.apikey);
|
||||
console.log('region: ', auth.region);
|
||||
|
||||
```
|
||||
|
||||
<sup>* _Remember to instantiate class before use_</sup>
|
||||
@@ -19,7 +19,7 @@ const connection = new ewelink({
|
||||
});
|
||||
|
||||
// login into eWeLink
|
||||
await connection.login();
|
||||
await connection.getCredentials();
|
||||
|
||||
// call openWebSocket method with a callback as argument
|
||||
const socket = await connection.openWebSocket(async data => {
|
||||
|
||||
@@ -24,6 +24,7 @@ Possible states: `on`, `off`, `toggle`.
|
||||
```js
|
||||
{
|
||||
status: 'ok',
|
||||
state: 'on'
|
||||
state: 'on',
|
||||
channel: 1
|
||||
}
|
||||
```
|
||||
@@ -11,6 +11,15 @@
|
||||
});
|
||||
```
|
||||
|
||||
**_Using phone number and password_**
|
||||
```
|
||||
const connection = new ewelink({
|
||||
phoneNumber: '<your phone number>',
|
||||
password: '<your ewelink password>',
|
||||
region: '<your ewelink region>',
|
||||
});
|
||||
```
|
||||
|
||||
**_Using access token and api key_**
|
||||
```
|
||||
const connection = new ewelink({
|
||||
@@ -20,5 +29,8 @@
|
||||
});
|
||||
```
|
||||
|
||||
**_Using devices and arp table cache files_**
|
||||
Check [ZeroConf](zeroconf.md) docs for detailed information.
|
||||
|
||||
> * If you don't know your region, use [getRegion](available-methods/getregion) method
|
||||
> * To get your access token and api key, use [getCredentials](available-methods/getcredentials) method
|
||||
@@ -17,11 +17,11 @@ So, instead of using email and password on every api call, you can login the fir
|
||||
region: '<your ewelink region>',
|
||||
});
|
||||
|
||||
const login = await connection.login();
|
||||
const credentials = await connection.getCredentials();
|
||||
|
||||
const accessToken = login.at;
|
||||
const apiKey = login.user.apikey
|
||||
const region = login.region;
|
||||
const accessToken = credentials.at;
|
||||
const apiKey = credentials.user.apikey
|
||||
const region = credentials.region;
|
||||
|
||||
})();
|
||||
```
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
# Testing
|
||||
|
||||
Open `test/_setup/credentials.json` and update parameters.
|
||||
## Run test suite
|
||||
Copy `test/_setup/config/credentials.example.js` to `test/_setup/config/credentials.js` and update parameters with yours.
|
||||
|
||||
In a terminal, `npm run test` or `npm run coverage`.
|
||||
|
||||
Tests needs to be performed serially to prevent flooding eWeLink servers, so if run jest manually, add `--runInBand` parameter.
|
||||
|
||||
> tests needs to be performed serially, so if run jest manually, add `--runInBand` parameter.
|
||||
> All devices needs to be connected before running test suite.
|
||||
|
||||
|
||||
## Using nock
|
||||
Running tests can take some time because there is many requests to eWeLink servers.
|
||||
|
||||
To speedup this process, you need to enable nock "record & play" feature by opening `test/_setup/setupTests.js` and change `nockAction` to `record` or `play`.
|
||||
|
||||
The first time you need to record all your requests then you can keep testing by "playing" recorded data.
|
||||
|
||||
Recorded data will be stored on `test/_setup/tapes` and you can delete folder content anytime.
|
||||
|
||||
Set `nockAction` to `false` to disable all nock functionality.
|
||||
|
||||
|
||||
## ZeroConf cache
|
||||
While testing ZeroConf functionalty, two temporary files will be created: `test/_setup/cache/arp-table.json` and `test/_setup/cache devices-cache.json`. These files can be safely deleted once tests finished.
|
||||
@@ -35,7 +35,7 @@ A file named `devices-cache.json` will be created.
|
||||
## 2. Generate arp table cache file
|
||||
|
||||
```js
|
||||
const Zeroconf = require('ewelink-api/classes/Zeroconf');
|
||||
const Zeroconf = require('ewelink-api/src/classes/Zeroconf');
|
||||
|
||||
await Zeroconf.saveArpTable({
|
||||
ip: '<your network addres, ex: 192.168.5.1>'
|
||||
@@ -49,7 +49,7 @@ A file named `arp-table.json` will be created.
|
||||
|
||||
```js
|
||||
const ewelink = require('ewelink-api');
|
||||
const Zeroconf = require('ewelink-api/classes/Zeroconf');
|
||||
const Zeroconf = require('ewelink-api/src/classes/Zeroconf');
|
||||
|
||||
/* load cache files */
|
||||
const devicesCache = await Zeroconf.loadCachedDevices();
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
const _get = (obj, path, defaultValue = null) =>
|
||||
String.prototype.split
|
||||
.call(path, /[,[\].]+?/)
|
||||
.filter(Boolean)
|
||||
.reduce(
|
||||
(a, c) => (Object.hasOwnProperty.call(a, c) ? a[c] : defaultValue),
|
||||
obj
|
||||
);
|
||||
|
||||
module.exports = { _get };
|
||||
@@ -1,19 +0,0 @@
|
||||
const nonce = require('nonce')();
|
||||
|
||||
const { makeFakeIMEI } = require('../ewelink-helper');
|
||||
|
||||
const credentialsPayload = ({ email, password }) => ({
|
||||
email,
|
||||
password,
|
||||
version: 6,
|
||||
ts: `${Math.round(new Date().getTime() / 1000)}`,
|
||||
nonce: `${nonce()}`,
|
||||
appid: 'YzfeftUVcZ6twZw1OoVKPRFYTrGEg01Q',
|
||||
imei: makeFakeIMEI(),
|
||||
os: 'iOS',
|
||||
model: 'iPhone10,6',
|
||||
romVersion: '11.1.2',
|
||||
appVersion: '3.5.3',
|
||||
});
|
||||
|
||||
module.exports = credentialsPayload;
|
||||
@@ -1,13 +0,0 @@
|
||||
const firmwareUpdate = require('./firmwareUpdate');
|
||||
const credentialsPayload = require('./credentialsPayload');
|
||||
const wssLoginPayload = require('./wssLoginPayload');
|
||||
const wssUpdatePayload = require('./wssUpdatePayload');
|
||||
const zeroConfUpdatePayload = require('./zeroConfUpdatePayload');
|
||||
|
||||
module.exports = {
|
||||
firmwareUpdate,
|
||||
credentialsPayload,
|
||||
wssLoginPayload,
|
||||
wssUpdatePayload,
|
||||
zeroConfUpdatePayload,
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
const nonce = require('nonce')();
|
||||
|
||||
const wssLoginPayload = ({ at, apiKey }) => {
|
||||
const timeStamp = new Date() / 1000;
|
||||
const ts = Math.floor(timeStamp);
|
||||
const sequence = Math.floor(timeStamp * 1000);
|
||||
const payload = {
|
||||
action: 'userOnline',
|
||||
userAgent: 'app',
|
||||
version: 6,
|
||||
nonce: `${nonce()}`,
|
||||
apkVesrion: '1.8',
|
||||
os: 'ios',
|
||||
at,
|
||||
apikey: apiKey,
|
||||
ts: `${ts}`,
|
||||
model: 'iPhone10,6',
|
||||
romVersion: '11.1.2',
|
||||
sequence,
|
||||
};
|
||||
return JSON.stringify(payload);
|
||||
};
|
||||
|
||||
module.exports = wssLoginPayload;
|
||||
163
main.js
163
main.js
@@ -1,22 +1,34 @@
|
||||
const rp = require('request-promise');
|
||||
|
||||
const { _get } = require('./lib/helpers');
|
||||
const mixins = require('./src/mixins');
|
||||
const errors = require('./src/data/errors');
|
||||
|
||||
class eWeLink {
|
||||
constructor({
|
||||
region = 'us',
|
||||
email,
|
||||
password,
|
||||
at,
|
||||
apiKey,
|
||||
devicesCache,
|
||||
arpTable,
|
||||
email = null,
|
||||
phoneNumber = null,
|
||||
password = null,
|
||||
at = null,
|
||||
apiKey = null,
|
||||
devicesCache = null,
|
||||
arpTable = null,
|
||||
}) {
|
||||
if (!devicesCache && !arpTable && !at && (!email && !password)) {
|
||||
return { error: 'No credentials provided' };
|
||||
const check = this.checkLoginParameters({
|
||||
region,
|
||||
email,
|
||||
phoneNumber,
|
||||
password,
|
||||
at,
|
||||
apiKey,
|
||||
devicesCache,
|
||||
arpTable,
|
||||
});
|
||||
|
||||
if (check === false) {
|
||||
throw new Error(errors.invalidCredentials);
|
||||
}
|
||||
|
||||
this.region = region;
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.at = at;
|
||||
@@ -25,6 +37,26 @@ class eWeLink {
|
||||
this.arpTable = arpTable;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
checkLoginParameters(params) {
|
||||
const { email, phoneNumber, password, devicesCache, arpTable, at } = params;
|
||||
|
||||
if (email !== null && phoneNumber !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
(email !== null && password !== null) ||
|
||||
(phoneNumber !== null && password !== null) ||
|
||||
(devicesCache !== null && arpTable !== null) ||
|
||||
at !== null
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate eWeLink API URL
|
||||
*
|
||||
@@ -57,116 +89,11 @@ class eWeLink {
|
||||
* @returns {string}
|
||||
*/
|
||||
getZeroconfUrl(device) {
|
||||
const ip = this.getLocalIp(device);
|
||||
const ip = this.getDeviceIP(device);
|
||||
return `http://${ip}:8081/zeroconf`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate http requests helpers
|
||||
*
|
||||
* @param method
|
||||
* @param url
|
||||
* @param uri
|
||||
* @param body
|
||||
* @param qs
|
||||
*
|
||||
* @returns {Promise<{msg: string, error: *}>}
|
||||
*/
|
||||
async makeRequest({ method = 'GET', url, uri, body = {}, qs = {} }) {
|
||||
const { at } = this;
|
||||
|
||||
if (!at) {
|
||||
await this.getCredentials();
|
||||
}
|
||||
|
||||
let apiUrl = this.getApiUrl();
|
||||
|
||||
if (url) {
|
||||
apiUrl = url;
|
||||
}
|
||||
|
||||
const response = await rp({
|
||||
method,
|
||||
uri: `${apiUrl}${uri}`,
|
||||
headers: { Authorization: `Bearer ${this.at}` },
|
||||
body,
|
||||
qs,
|
||||
json: true,
|
||||
});
|
||||
|
||||
const error = _get(response, 'error', false);
|
||||
if (error && [401, 402].indexOf(parseInt(error)) !== -1) {
|
||||
return { error, msg: 'Authentication error' };
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/* LOAD MIXINS: user */
|
||||
const getCredentialsMixin = require('./mixins/user/getCredentialsMixin');
|
||||
const getRegionMixin = require('./mixins/user/getRegionMixin');
|
||||
|
||||
/* LOAD MIXINS: power state */
|
||||
const getDevicePowerStateMixin = require('./mixins/powerState/getDevicePowerStateMixin');
|
||||
const setDevicePowerState = require('./mixins/powerState/setDevicePowerStateMixin');
|
||||
const toggleDeviceMixin = require('./mixins/powerState/toggleDeviceMixin');
|
||||
|
||||
/* LOAD MIXINS: power usage */
|
||||
const getDevicePowerUsageMixin = require('./mixins/powerUsage/getDevicePowerUsageMixin');
|
||||
const getDeviceRawPowerUsageMixin = require('./mixins/powerUsage/getDeviceRawPowerUsageMixin');
|
||||
|
||||
/* LOAD MIXINS: temperature & humidity */
|
||||
const getTHMixin = require('./mixins/temphumd/getTHMixin');
|
||||
|
||||
/* LOAD MIXINS: devices */
|
||||
const getDevicesMixin = require('./mixins/devices/getDevicesMixin');
|
||||
const getDeviceMixin = require('./mixins/devices/getDeviceMixin');
|
||||
const getDeviceChannelCountMixin = require('./mixins/devices/getDeviceChannelCountMixin');
|
||||
const getLocalIpMixin = require('./mixins/devices/getLocalIpMixin');
|
||||
const saveDevicesCacheMixin = require('./mixins/devices/saveDevicesCacheMixin');
|
||||
|
||||
/* LOAD MIXINS: firmware */
|
||||
const getFirmwareVersionMixin = require('./mixins/firmware/getFirmwareVersionMixin');
|
||||
const checkDeviceUpdateMixin = require('./mixins/firmware/checkDeviceUpdateMixin');
|
||||
const checkDevicesUpdatesMixin = require('./mixins/firmware/checkDevicesUpdatesMixin');
|
||||
|
||||
/* LOAD MIXINS: websocket */
|
||||
const openWebSocketMixin = require('./mixins/websocket/openWebSocketMixin');
|
||||
|
||||
Object.assign(eWeLink.prototype, getCredentialsMixin, getRegionMixin);
|
||||
|
||||
Object.assign(
|
||||
eWeLink.prototype,
|
||||
getDevicePowerStateMixin,
|
||||
setDevicePowerState,
|
||||
toggleDeviceMixin
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
eWeLink.prototype,
|
||||
getDevicePowerUsageMixin,
|
||||
getDeviceRawPowerUsageMixin
|
||||
);
|
||||
|
||||
Object.assign(eWeLink.prototype, getTHMixin);
|
||||
|
||||
Object.assign(
|
||||
eWeLink.prototype,
|
||||
getDevicesMixin,
|
||||
getDeviceMixin,
|
||||
getDeviceChannelCountMixin,
|
||||
getLocalIpMixin,
|
||||
saveDevicesCacheMixin
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
eWeLink.prototype,
|
||||
getFirmwareVersionMixin,
|
||||
checkDeviceUpdateMixin,
|
||||
checkDevicesUpdatesMixin
|
||||
);
|
||||
|
||||
Object.assign(eWeLink.prototype, openWebSocketMixin);
|
||||
Object.assign(eWeLink.prototype, mixins);
|
||||
|
||||
module.exports = eWeLink;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
|
||||
const getDeviceMixin = {
|
||||
/**
|
||||
* Get information about all associated devices to account
|
||||
*
|
||||
* @param deviceId
|
||||
*
|
||||
* @returns {Promise<{msg: string, error: *}>}
|
||||
*/
|
||||
async getDevice(deviceId) {
|
||||
if (this.devicesCache) {
|
||||
return this.devicesCache.find(dev => dev.deviceid === deviceId) || null;
|
||||
}
|
||||
|
||||
const devices = await this.getDevices();
|
||||
|
||||
const error = _get(devices, 'error', false);
|
||||
|
||||
if (error === 406) {
|
||||
return { error: 401, msg: 'Authentication error' };
|
||||
}
|
||||
|
||||
if (error || !devices) {
|
||||
return devices;
|
||||
}
|
||||
|
||||
const device = devices.find(dev => dev.deviceid === deviceId);
|
||||
|
||||
if (!device) {
|
||||
return { error: 500, msg: 'Device does not exist' };
|
||||
}
|
||||
|
||||
return device;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getDeviceMixin;
|
||||
@@ -1,45 +0,0 @@
|
||||
const { makeFakeIMEI } = require('../../lib/ewelink-helper');
|
||||
const { _get } = require('../../lib/helpers');
|
||||
|
||||
const getDevicesMixin = {
|
||||
/**
|
||||
* Get all devices information
|
||||
*
|
||||
* @returns {Promise<{msg: string, error: number}|*>}
|
||||
*/
|
||||
async getDevices() {
|
||||
const timeStamp = new Date() / 1000;
|
||||
const ts = Math.floor(timeStamp);
|
||||
|
||||
const response = await this.makeRequest({
|
||||
uri: '/user/device',
|
||||
qs: {
|
||||
lang: 'en',
|
||||
getTags: 1,
|
||||
version: 6,
|
||||
ts,
|
||||
appid: 'YzfeftUVcZ6twZw1OoVKPRFYTrGEg01Q',
|
||||
imei: makeFakeIMEI(),
|
||||
os: 'android',
|
||||
model: '',
|
||||
romVersion: '',
|
||||
appVersion: '3.12.0',
|
||||
},
|
||||
});
|
||||
|
||||
const error = _get(response, 'error', false);
|
||||
const devicelist = _get(response, 'devicelist', false);
|
||||
|
||||
if (error === 406) {
|
||||
return { error: 401, msg: 'Authentication error' };
|
||||
}
|
||||
|
||||
if (!devicelist) {
|
||||
return { error: 500, msg: 'No devices found' };
|
||||
}
|
||||
|
||||
return devicelist;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getDevicesMixin;
|
||||
@@ -1,43 +0,0 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
|
||||
const { getDeviceChannelCount } = require('../../lib/ewelink-helper');
|
||||
|
||||
const getDevicePowerStateMixin = {
|
||||
/**
|
||||
* Get current power state for a specific device
|
||||
*
|
||||
* @param deviceId
|
||||
* @param channel
|
||||
*
|
||||
* @returns {Promise<{state: *, status: string}|{msg: string, error: *}>}
|
||||
*/
|
||||
async getDevicePowerState(deviceId, channel = 1) {
|
||||
const device = await this.getDevice(deviceId);
|
||||
const error = _get(device, 'error', false);
|
||||
const uiid = _get(device, 'extra.extra.uiid', false);
|
||||
|
||||
let state = _get(device, 'params.switch', false);
|
||||
const switches = _get(device, 'params.switches', false);
|
||||
|
||||
const switchesAmount = getDeviceChannelCount(uiid);
|
||||
|
||||
if (switchesAmount > 0 && switchesAmount < channel) {
|
||||
return { error, msg: 'Device channel does not exist' };
|
||||
}
|
||||
|
||||
if (error || (!state && !switches)) {
|
||||
if (error && parseInt(error) === 401) {
|
||||
return device;
|
||||
}
|
||||
return { error, msg: 'Device does not exist' };
|
||||
}
|
||||
|
||||
if (switches) {
|
||||
state = switches[channel - 1].switch;
|
||||
}
|
||||
|
||||
return { status: 'ok', state };
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getDevicePowerStateMixin;
|
||||
7127
package-lock.json
generated
7127
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ewelink-api",
|
||||
"version": "2.0.1",
|
||||
"version": "3.0.0",
|
||||
"description": "eWeLink API for Node.js",
|
||||
"author": "Martín M.",
|
||||
"license": "MIT",
|
||||
@@ -27,6 +27,9 @@
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"jest": {
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/test/_setup/setupTests.js"
|
||||
],
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
".cache"
|
||||
@@ -38,28 +41,27 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"arpping": "github:skydiver/arpping",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"crypto-js": "^4.0.0",
|
||||
"delay": "^4.3.0",
|
||||
"nonce": "^1.0.4",
|
||||
"random": "^2.1.1",
|
||||
"request": "^2.88.0",
|
||||
"request-promise": "^4.2.4",
|
||||
"websocket": "^1.0.30",
|
||||
"websocket-as-promised": "^0.10.1"
|
||||
"node-fetch": "^2.6.0",
|
||||
"random": "^2.2.0",
|
||||
"websocket": "^1.0.31",
|
||||
"websocket-as-promised": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.3",
|
||||
"eslint": "^6.4.0",
|
||||
"eslint-config-airbnb": "^18.0.1",
|
||||
"eslint-config-prettier": "^6.3.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.0.0",
|
||||
"eslint-config-airbnb": "^18.1.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-config-wesbos": "0.0.19",
|
||||
"eslint-plugin-html": "^6.0.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-html": "^6.0.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"eslint-plugin-react": "^7.14.3",
|
||||
"eslint-plugin-react-hooks": "^2.0.1",
|
||||
"jest": "^24.9.0",
|
||||
"prettier": "^1.18.2"
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.20.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.2",
|
||||
"jest": "^26.0.1",
|
||||
"nock": "^12.0.3",
|
||||
"prettier": "^1.19.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const rp = require('request-promise');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const WebSocket = require('../WebSocket');
|
||||
const payloads = require('../../lib/payloads');
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const WebSocket = require('./WebSocket');
|
||||
const zeroConfUpdatePayload = require('../payloads/zeroConfUpdatePayload');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
|
||||
class ChangeStateZeroconf extends WebSocket {
|
||||
static async set({ url, device, params, switches, state }) {
|
||||
@@ -11,22 +11,16 @@ class ChangeStateZeroconf extends WebSocket {
|
||||
const deviceKey = device.devicekey;
|
||||
|
||||
const endpoint = switches ? 'switches' : 'switch';
|
||||
const localUrl = `${url}/${endpoint}`;
|
||||
|
||||
const body = payloads.zeroConfUpdatePayload(
|
||||
selfApikey,
|
||||
deviceId,
|
||||
deviceKey,
|
||||
params
|
||||
);
|
||||
const body = zeroConfUpdatePayload(selfApikey, deviceId, deviceKey, params);
|
||||
|
||||
const response = await rp({
|
||||
method: 'POST',
|
||||
uri: localUrl,
|
||||
body,
|
||||
json: true,
|
||||
const request = await fetch(`${url}/${endpoint}`, {
|
||||
method: 'post',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const response = await request.json();
|
||||
|
||||
const error = _get(response, 'error', false);
|
||||
|
||||
if (error === 403) {
|
||||
@@ -1,8 +1,10 @@
|
||||
const WebSocket = require('../WebSocket');
|
||||
const payloads = require('../../lib/payloads');
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const WebSocket = require('./WebSocket');
|
||||
const wssLoginPayload = require('../payloads/wssLoginPayload');
|
||||
const wssUpdatePayload = require('../payloads/wssUpdatePayload');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
class DeviceRaw extends WebSocket {
|
||||
class DevicePowerUsageRaw extends WebSocket {
|
||||
/**
|
||||
* Get specific device power usage (raw data)
|
||||
*
|
||||
@@ -10,13 +12,12 @@ class DeviceRaw extends WebSocket {
|
||||
* @param at
|
||||
* @param apiKey
|
||||
* @param deviceId
|
||||
*
|
||||
* @returns {Promise<{error: string}|{data: {hundredDaysKwhData: *}, status: string}>}
|
||||
* @returns {Promise<{error: string}|{data: {hundredDaysKwhData: *}, status: string}|{msg: any, error: *}|{msg: string, error: number}>}
|
||||
*/
|
||||
static async get({ apiUrl, at, apiKey, deviceId }) {
|
||||
const payloadLogin = payloads.wssLoginPayload({ at, apiKey });
|
||||
const payloadLogin = wssLoginPayload({ at, apiKey });
|
||||
|
||||
const payloadUpdate = payloads.wssUpdatePayload({
|
||||
const payloadUpdate = wssUpdatePayload({
|
||||
apiKey,
|
||||
deviceId,
|
||||
params: { hundredDaysKwh: 'get' },
|
||||
@@ -27,6 +28,10 @@ class DeviceRaw extends WebSocket {
|
||||
payloadUpdate,
|
||||
]);
|
||||
|
||||
if (response.length === 1) {
|
||||
return { error: errors.noPower };
|
||||
}
|
||||
|
||||
const error = _get(response[1], 'error', false);
|
||||
|
||||
if (error === 403) {
|
||||
@@ -40,7 +45,7 @@ class DeviceRaw extends WebSocket {
|
||||
);
|
||||
|
||||
if (!hundredDaysKwhData) {
|
||||
return { error: 'No power usage data found.' };
|
||||
return { error: errors.noPower };
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -50,4 +55,4 @@ class DeviceRaw extends WebSocket {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeviceRaw;
|
||||
module.exports = DevicePowerUsageRaw;
|
||||
@@ -2,6 +2,8 @@ const W3CWebSocket = require('websocket').w3cwebsocket;
|
||||
const WebSocketAsPromised = require('websocket-as-promised');
|
||||
const delay = require('delay');
|
||||
|
||||
const errors = require('../data/errors');
|
||||
|
||||
class WebSocket {
|
||||
/**
|
||||
* Open WebSocket connection and send provided payloads
|
||||
@@ -46,9 +48,9 @@ class WebSocket {
|
||||
static customThrowError(e) {
|
||||
const loginError = e.message.indexOf('WebSocket is not opened');
|
||||
if (loginError > -1) {
|
||||
return { error: 401, msg: 'Authentication error' };
|
||||
return { error: 406, msg: errors['406'] };
|
||||
}
|
||||
return { error: 'An unknown error occurred' };
|
||||
return { error: errors.unknown };
|
||||
}
|
||||
}
|
||||
|
||||
7
src/data/constants.js
Normal file
7
src/data/constants.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const APP_ID = 'YzfeftUVcZ6twZw1OoVKPRFYTrGEg01Q';
|
||||
const APP_SECRET = '4G91qSoboqYO4Y0XJ0LPPKIsq8reHdfa';
|
||||
|
||||
module.exports = {
|
||||
APP_ID,
|
||||
APP_SECRET,
|
||||
};
|
||||
21
src/data/errors.js
Normal file
21
src/data/errors.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const errors = {
|
||||
400: 'Parameter error',
|
||||
401: 'Wrong account or password',
|
||||
402: 'Email inactivated',
|
||||
403: 'Forbidden',
|
||||
404: 'Device does not exist',
|
||||
406: 'Authentication failed',
|
||||
};
|
||||
|
||||
const customErrors = {
|
||||
ch404: 'Device channel does not exist',
|
||||
unknown: 'An unknown error occurred',
|
||||
noDevices: 'No devices found',
|
||||
noPower: 'No power usage data found',
|
||||
noSensor: "Can't read sensor data from device",
|
||||
noFirmware: "Can't get model or firmware version",
|
||||
invalidAuth: 'Library needs to be initialized using email and password',
|
||||
invalidCredentials: 'Invalid credentials provided',
|
||||
};
|
||||
|
||||
module.exports = Object.assign(errors, customErrors);
|
||||
@@ -2,18 +2,14 @@ const crypto = require('crypto');
|
||||
const CryptoJS = require('crypto-js');
|
||||
const random = require('random');
|
||||
|
||||
const DEVICE_TYPE_UUID = require('./data/devices-type-uuid');
|
||||
const DEVICE_CHANNEL_LENGTH = require('./data/devices-channel-length');
|
||||
const { APP_SECRET } = require('../data/constants');
|
||||
|
||||
const makeFakeIMEI = () => {
|
||||
const num1 = random.int(1000, 9999);
|
||||
const num2 = random.int(1000, 9999);
|
||||
return `DF7425A0-${num1}-${num2}-9F5E-3BC9179E48FB`;
|
||||
};
|
||||
const DEVICE_TYPE_UUID = require('../data/devices-type-uuid.json');
|
||||
const DEVICE_CHANNEL_LENGTH = require('../data/devices-channel-length.json');
|
||||
|
||||
const makeAuthorizationSign = body =>
|
||||
crypto
|
||||
.createHmac('sha256', '4G91qSoboqYO4Y0XJ0LPPKIsq8reHdfa')
|
||||
.createHmac('sha256', APP_SECRET)
|
||||
.update(JSON.stringify(body))
|
||||
.digest('base64');
|
||||
|
||||
@@ -68,7 +64,6 @@ const decryptionData = (data, key, iv) => {
|
||||
|
||||
module.exports = {
|
||||
makeAuthorizationSign,
|
||||
makeFakeIMEI,
|
||||
getDeviceChannelCount,
|
||||
encryptationData,
|
||||
decryptionData,
|
||||
29
src/helpers/utilities.js
Normal file
29
src/helpers/utilities.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const nonce = Math.random()
|
||||
.toString(36)
|
||||
.slice(5);
|
||||
|
||||
const timestamp = Math.floor(new Date() / 1000);
|
||||
|
||||
const _get = (obj, path, defaultValue = null) =>
|
||||
String.prototype.split
|
||||
.call(path, /[,[\].]+?/)
|
||||
.filter(Boolean)
|
||||
.reduce(
|
||||
(a, c) => (Object.hasOwnProperty.call(a, c) ? a[c] : defaultValue),
|
||||
obj
|
||||
);
|
||||
|
||||
const _empty = obj => Object.entries(obj).length === 0;
|
||||
|
||||
const toQueryString = object =>
|
||||
`?${Object.keys(object)
|
||||
.map(key => `${key}=${object[key].toString()}`)
|
||||
.join('&')}`;
|
||||
|
||||
module.exports = {
|
||||
nonce,
|
||||
timestamp,
|
||||
_get,
|
||||
_empty,
|
||||
toQueryString,
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const payloads = require('../../lib/payloads');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const parseFirmwareUpdates = require('../parsers/parseFirmwareUpdates');
|
||||
|
||||
const checkDeviceUpdateMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Check device firmware update
|
||||
*
|
||||
@@ -18,7 +18,7 @@ const checkDeviceUpdateMixin = {
|
||||
return device;
|
||||
}
|
||||
|
||||
const deviceInfoList = payloads.firmwareUpdate([device]);
|
||||
const deviceInfoList = parseFirmwareUpdates([device]);
|
||||
|
||||
const deviceInfoListError = _get(deviceInfoList, 'error', false);
|
||||
|
||||
@@ -27,7 +27,7 @@ const checkDeviceUpdateMixin = {
|
||||
}
|
||||
|
||||
const update = await this.makeRequest({
|
||||
method: 'POST',
|
||||
method: 'post',
|
||||
url: this.getOtaUrl(),
|
||||
uri: '/app',
|
||||
body: { deviceInfoList },
|
||||
@@ -46,5 +46,3 @@ const checkDeviceUpdateMixin = {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = checkDeviceUpdateMixin;
|
||||
@@ -1,7 +1,7 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const payloads = require('../../lib/payloads');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const parseFirmwareUpdates = require('../parsers/parseFirmwareUpdates');
|
||||
|
||||
const checkDevicesUpdatesMixin = {
|
||||
module.exports = {
|
||||
async checkDevicesUpdates() {
|
||||
const devices = await this.getDevices();
|
||||
|
||||
@@ -11,7 +11,7 @@ const checkDevicesUpdatesMixin = {
|
||||
return devices;
|
||||
}
|
||||
|
||||
const deviceInfoList = payloads.firmwareUpdate(devices);
|
||||
const deviceInfoList = parseFirmwareUpdates(devices);
|
||||
|
||||
const deviceInfoListError = _get(deviceInfoList, 'error', false);
|
||||
|
||||
@@ -20,7 +20,7 @@ const checkDevicesUpdatesMixin = {
|
||||
}
|
||||
|
||||
const updates = await this.makeRequest({
|
||||
method: 'POST',
|
||||
method: 'post',
|
||||
url: this.getOtaUrl(),
|
||||
uri: '/app',
|
||||
body: { deviceInfoList },
|
||||
@@ -52,5 +52,3 @@ const checkDevicesUpdatesMixin = {
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = checkDevicesUpdatesMixin;
|
||||
@@ -1,10 +1,11 @@
|
||||
const rp = require('request-promise');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { credentialsPayload } = require('../../lib/payloads');
|
||||
const { makeAuthorizationSign } = require('../../lib/ewelink-helper');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const credentialsPayload = require('../payloads/credentialsPayload');
|
||||
const { makeAuthorizationSign } = require('../helpers/ewelink');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
const getCredentialsMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Returns user credentials information
|
||||
*
|
||||
@@ -13,22 +14,23 @@ const getCredentialsMixin = {
|
||||
async getCredentials() {
|
||||
const body = credentialsPayload({
|
||||
email: this.email,
|
||||
phoneNumber: this.phoneNumber,
|
||||
password: this.password,
|
||||
});
|
||||
|
||||
let response = await rp({
|
||||
method: 'POST',
|
||||
uri: `${this.getApiUrl()}/user/login`,
|
||||
const request = await fetch(`${this.getApiUrl()}/user/login`, {
|
||||
method: 'post',
|
||||
headers: { Authorization: `Sign ${makeAuthorizationSign(body)}` },
|
||||
body,
|
||||
json: true,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
let response = await request.json();
|
||||
|
||||
const error = _get(response, 'error', false);
|
||||
const region = _get(response, 'region', false);
|
||||
|
||||
if (error && [400, 401, 404].indexOf(parseInt(error)) !== -1) {
|
||||
return { error, msg: 'Authentication error' };
|
||||
return { error: 406, msg: errors['406'] };
|
||||
}
|
||||
|
||||
if (error && parseInt(error) === 301 && region) {
|
||||
@@ -45,5 +47,3 @@ const getCredentialsMixin = {
|
||||
return response;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getCredentialsMixin;
|
||||
36
src/mixins/getDevice.js
Normal file
36
src/mixins/getDevice.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const { APP_ID } = require('../data/constants');
|
||||
const { nonce, timestamp, _get } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Get information for a specific device
|
||||
*
|
||||
* @param deviceId
|
||||
* @returns {Promise<*|null|{msg: string, error: *}>}
|
||||
*/
|
||||
async getDevice(deviceId) {
|
||||
if (this.devicesCache) {
|
||||
return this.devicesCache.find(dev => dev.deviceid === deviceId) || null;
|
||||
}
|
||||
|
||||
const device = await this.makeRequest({
|
||||
uri: `/user/device/${deviceId}`,
|
||||
qs: {
|
||||
deviceid: deviceId,
|
||||
appid: APP_ID,
|
||||
nonce,
|
||||
ts: timestamp,
|
||||
version: 8,
|
||||
},
|
||||
});
|
||||
|
||||
const error = _get(device, 'error', false);
|
||||
|
||||
if (error) {
|
||||
return { error, msg: errors[error] };
|
||||
}
|
||||
|
||||
return device;
|
||||
},
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
const { getDeviceChannelCount } = require('../../lib/ewelink-helper');
|
||||
const { getDeviceChannelCount } = require('../helpers/ewelink');
|
||||
|
||||
const getDeviceChannelCountMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Get device channel count
|
||||
*
|
||||
@@ -17,14 +18,9 @@ const getDeviceChannelCountMixin = {
|
||||
const switchesAmount = getDeviceChannelCount(uiid);
|
||||
|
||||
if (error) {
|
||||
if (error === 401) {
|
||||
return device;
|
||||
}
|
||||
return { error, msg: 'Device does not exist' };
|
||||
return { error, msg: errors[error] };
|
||||
}
|
||||
|
||||
return { status: 'ok', switchesAmount };
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getDeviceChannelCountMixin;
|
||||
@@ -1,6 +1,7 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
const getTHMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Get device current temperature & humidity
|
||||
* @param deviceId
|
||||
@@ -18,7 +19,7 @@ const getTHMixin = {
|
||||
}
|
||||
|
||||
if (!temperature || !humidity) {
|
||||
return { error: 500, msg: "Can't read sensor data from device" };
|
||||
return { error: 404, msg: errors.noSensor };
|
||||
}
|
||||
|
||||
const data = { status: 'ok', temperature, humidity };
|
||||
@@ -52,5 +53,3 @@ const getTHMixin = {
|
||||
return this.getDeviceCurrentTH(deviceId, 'humd');
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getTHMixin;
|
||||
@@ -1,11 +1,11 @@
|
||||
const getLocalIpMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Get local IP address from a given MAC
|
||||
*
|
||||
* @param device
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
getLocalIp(device) {
|
||||
getDeviceIP(device) {
|
||||
const mac = device.extra.extra.staMac;
|
||||
const arpItem = this.arpTable.find(
|
||||
item => item.mac.toLowerCase() === mac.toLowerCase()
|
||||
@@ -13,5 +13,3 @@ const getLocalIpMixin = {
|
||||
return arpItem.ip;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getLocalIpMixin;
|
||||
43
src/mixins/getDevicePowerState.js
Normal file
43
src/mixins/getDevicePowerState.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
const deviceStatusPayload = require('../payloads/deviceStatus');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Get current power state for a specific device
|
||||
*
|
||||
* @param deviceId
|
||||
* @param channel
|
||||
*
|
||||
* @returns {Promise<{state: *, status: string}|{msg: string, error: *}>}
|
||||
*/
|
||||
async getDevicePowerState(deviceId, channel = 1) {
|
||||
const status = await this.makeRequest({
|
||||
uri: '/user/device/status',
|
||||
qs: deviceStatusPayload({ deviceId, params: 'switch|switches' }),
|
||||
});
|
||||
|
||||
const error = _get(status, 'error', false);
|
||||
|
||||
if (error) {
|
||||
const err = error === 400 ? 404 : error;
|
||||
return { error: err, msg: errors[err] };
|
||||
}
|
||||
|
||||
let state = _get(status, 'params.switch', false);
|
||||
const switches = _get(status, 'params.switches', false);
|
||||
|
||||
const switchesAmount = switches ? switches.length : 1;
|
||||
|
||||
if (switchesAmount > 0 && switchesAmount < channel) {
|
||||
return { error: 404, msg: errors.ch404 };
|
||||
}
|
||||
|
||||
if (switches) {
|
||||
state = switches[channel - 1].switch;
|
||||
}
|
||||
|
||||
return { status: 'ok', state, channel };
|
||||
},
|
||||
};
|
||||
@@ -1,8 +1,7 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const parsePowerUsage = require('../parsers/parsePowerUsage');
|
||||
|
||||
const { CurrentMonth } = require('../../classes/PowerUsage');
|
||||
|
||||
const getDevicePowerUsageMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Get device power usage for current month
|
||||
*
|
||||
@@ -11,7 +10,7 @@ const getDevicePowerUsageMixin = {
|
||||
* @returns {Promise<{error: string}|{daily: *, monthly: *}>}
|
||||
*/
|
||||
async getDevicePowerUsage(deviceId) {
|
||||
const response = await this.getDeviceRawPowerUsage(deviceId);
|
||||
const response = await this.getDevicePowerUsageRaw(deviceId);
|
||||
|
||||
const error = _get(response, 'error', false);
|
||||
const hundredDaysKwhData = _get(response, 'data.hundredDaysKwhData', false);
|
||||
@@ -22,9 +21,7 @@ const getDevicePowerUsageMixin = {
|
||||
|
||||
return {
|
||||
status: 'ok',
|
||||
...CurrentMonth.parse({ hundredDaysKwhData }),
|
||||
...parsePowerUsage({ hundredDaysKwhData }),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getDevicePowerUsageMixin;
|
||||
@@ -1,8 +1,8 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
|
||||
const { DeviceRaw } = require('../../classes/PowerUsage');
|
||||
const DevicePowerUsageRaw = require('../classes/DevicePowerUsageRaw');
|
||||
|
||||
const getDeviceRawPowerUsageMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Get device raw power usage
|
||||
*
|
||||
@@ -10,7 +10,7 @@ const getDeviceRawPowerUsageMixin = {
|
||||
*
|
||||
* @returns {Promise<{error: string}|{response: {hundredDaysKwhData: *}, status: string}>}
|
||||
*/
|
||||
async getDeviceRawPowerUsage(deviceId) {
|
||||
async getDevicePowerUsageRaw(deviceId) {
|
||||
const device = await this.getDevice(deviceId);
|
||||
const deviceApiKey = _get(device, 'apikey', false);
|
||||
|
||||
@@ -25,8 +25,6 @@ const getDeviceRawPowerUsageMixin = {
|
||||
actionParams.apiKey = deviceApiKey;
|
||||
}
|
||||
|
||||
return DeviceRaw.get(actionParams);
|
||||
return DevicePowerUsageRaw.get(actionParams);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getDeviceRawPowerUsageMixin;
|
||||
36
src/mixins/getDevices.js
Normal file
36
src/mixins/getDevices.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const { APP_ID } = require('../data/constants');
|
||||
const { _get, timestamp } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Get all devices information
|
||||
*
|
||||
* @returns {Promise<{msg: string, error: number}|*>}
|
||||
*/
|
||||
async getDevices() {
|
||||
const response = await this.makeRequest({
|
||||
uri: '/user/device',
|
||||
qs: {
|
||||
lang: 'en',
|
||||
appid: APP_ID,
|
||||
ts: timestamp,
|
||||
version: 8,
|
||||
getTags: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const error = _get(response, 'error', false);
|
||||
const devicelist = _get(response, 'devicelist', false);
|
||||
|
||||
if (error) {
|
||||
return { error, msg: errors[error] };
|
||||
}
|
||||
|
||||
if (!devicelist) {
|
||||
return { error: 404, msg: errors.noDevices };
|
||||
}
|
||||
|
||||
return devicelist;
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
const getFirmwareVersionMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Get device firmware version
|
||||
*
|
||||
@@ -14,14 +15,9 @@ const getFirmwareVersionMixin = {
|
||||
const fwVersion = _get(device, 'params.fwVersion', false);
|
||||
|
||||
if (error || !fwVersion) {
|
||||
if (error === 401) {
|
||||
return device;
|
||||
}
|
||||
return { error, msg: 'Device does not exist' };
|
||||
return { error, msg: errors[error] };
|
||||
}
|
||||
|
||||
return { status: 'ok', fwVersion };
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getFirmwareVersionMixin;
|
||||
@@ -1,12 +1,10 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
const getRegionMixin = {
|
||||
module.exports = {
|
||||
async getRegion() {
|
||||
if (!this.email || !this.password) {
|
||||
return {
|
||||
error: 406,
|
||||
msg: 'Library needs to be initialized using email and password',
|
||||
};
|
||||
return { error: 406, msg: errors.invalidAuth };
|
||||
}
|
||||
|
||||
const credentials = await this.getCredentials();
|
||||
@@ -23,5 +21,3 @@ const getRegionMixin = {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = getRegionMixin;
|
||||
41
src/mixins/index.js
Normal file
41
src/mixins/index.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { checkDevicesUpdates } = require('./checkDevicesUpdates');
|
||||
const { checkDeviceUpdate } = require('./checkDeviceUpdate');
|
||||
const { getCredentials } = require('./getCredentials');
|
||||
const { getDevice } = require('./getDevice');
|
||||
const { getDeviceChannelCount } = require('./getDeviceChannelCount');
|
||||
const getDeviceCurrentTH = require('./getDeviceCurrentTH');
|
||||
const { getDeviceIP } = require('./getDeviceIP');
|
||||
const { getDevicePowerState } = require('./getDevicePowerState');
|
||||
const { getDevicePowerUsage } = require('./getDevicePowerUsage');
|
||||
const { getDevicePowerUsageRaw } = require('./getDevicePowerUsageRaw');
|
||||
const { getDevices } = require('./getDevices');
|
||||
const { getFirmwareVersion } = require('./getFirmwareVersion');
|
||||
const { getRegion } = require('./getRegion');
|
||||
const { makeRequest } = require('./makeRequest')
|
||||
const { openWebSocket } = require('./openWebSocket');
|
||||
const { saveDevicesCache } = require('./saveDevicesCache');
|
||||
const { setDevicePowerState } = require('./setDevicePowerState');
|
||||
const { toggleDevice } = require('./toggleDevice');
|
||||
|
||||
const mixins = {
|
||||
checkDevicesUpdates,
|
||||
checkDeviceUpdate,
|
||||
getCredentials,
|
||||
getDevice,
|
||||
getDeviceChannelCount,
|
||||
...getDeviceCurrentTH,
|
||||
getDeviceIP,
|
||||
getDevicePowerState,
|
||||
getDevicePowerUsage,
|
||||
getDevicePowerUsageRaw,
|
||||
getDevices,
|
||||
getFirmwareVersion,
|
||||
getRegion,
|
||||
makeRequest,
|
||||
openWebSocket,
|
||||
saveDevicesCache,
|
||||
setDevicePowerState,
|
||||
toggleDevice,
|
||||
};
|
||||
|
||||
module.exports = mixins;
|
||||
55
src/mixins/makeRequest.js
Normal file
55
src/mixins/makeRequest.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const fetch = require('node-fetch');
|
||||
const { _get, _empty, toQueryString } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Helper to make api requests
|
||||
*
|
||||
* @param method
|
||||
* @param url
|
||||
* @param uri
|
||||
* @param body
|
||||
* @param qs
|
||||
* @returns {Promise<{msg: *, error: *}|*>}
|
||||
*/
|
||||
async makeRequest({ method = 'get', url, uri, body = {}, qs = {} }) {
|
||||
const { at } = this;
|
||||
|
||||
if (!at) {
|
||||
await this.getCredentials();
|
||||
}
|
||||
|
||||
let apiUrl = this.getApiUrl();
|
||||
|
||||
if (url) {
|
||||
apiUrl = url;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
method,
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.at}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (!_empty(body)) {
|
||||
payload.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const queryString = !_empty(qs) ? toQueryString(qs) : '';
|
||||
const requestUrl = `${apiUrl}${uri}${queryString}`;
|
||||
|
||||
const request = await fetch(requestUrl, payload);
|
||||
const response = await request.json();
|
||||
|
||||
const error = _get(response, 'error', false);
|
||||
|
||||
if (error) {
|
||||
return { error, msg: errors[error] };
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
const W3CWebSocket = require('websocket').w3cwebsocket;
|
||||
const WebSocketAsPromised = require('websocket-as-promised');
|
||||
|
||||
const payloads = require('../../lib/payloads');
|
||||
const wssLoginPayload = require('../payloads/wssLoginPayload');
|
||||
|
||||
const openWebSocketMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Open a socket connection to eWeLink
|
||||
* and execute callback function with server message as argument
|
||||
@@ -13,7 +13,7 @@ const openWebSocketMixin = {
|
||||
* @returns {Promise<WebSocketAsPromised>}
|
||||
*/
|
||||
async openWebSocket(callback, ...{ heartbeat = 120000 }) {
|
||||
const payloadLogin = payloads.wssLoginPayload({
|
||||
const payloadLogin = wssLoginPayload({
|
||||
at: this.at,
|
||||
apiKey: this.apiKey,
|
||||
});
|
||||
@@ -41,5 +41,3 @@ const openWebSocketMixin = {
|
||||
return wsp;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = openWebSocketMixin;
|
||||
@@ -1,8 +1,8 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
|
||||
const saveDevicesCacheMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Save devices cache file (useful for using zeroconf)
|
||||
* @returns {Promise<string|{msg: string, error: number}|*|Device[]|{msg: string, error: number}>}
|
||||
@@ -13,7 +13,6 @@ const saveDevicesCacheMixin = {
|
||||
const error = _get(devices, 'error', false);
|
||||
|
||||
if (error || !devices) {
|
||||
console.log(devices);
|
||||
return devices;
|
||||
}
|
||||
|
||||
@@ -28,5 +27,3 @@ const saveDevicesCacheMixin = {
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = saveDevicesCacheMixin;
|
||||
@@ -1,12 +1,12 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { APP_ID } = require('../data/constants');
|
||||
const { _get, timestamp, nonce } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
const { getDeviceChannelCount } = require('../../lib/ewelink-helper');
|
||||
const {
|
||||
ChangeState,
|
||||
ChangeStateZeroconf,
|
||||
} = require('../../classes/PowerState');
|
||||
const { getDeviceChannelCount } = require('../helpers/ewelink');
|
||||
|
||||
const setDevicePowerState = {
|
||||
const ChangeStateZeroconf = require('../classes/ChangeStateZeroconf');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Change power state for a specific device
|
||||
*
|
||||
@@ -18,7 +18,6 @@ const setDevicePowerState = {
|
||||
*/
|
||||
async setDevicePowerState(deviceId, state, channel = 1) {
|
||||
const device = await this.getDevice(deviceId);
|
||||
const deviceApiKey = _get(device, 'apikey', false);
|
||||
const error = _get(device, 'error', false);
|
||||
const uiid = _get(device, 'extra.extra.uiid', false);
|
||||
|
||||
@@ -28,14 +27,11 @@ const setDevicePowerState = {
|
||||
const switchesAmount = getDeviceChannelCount(uiid);
|
||||
|
||||
if (switchesAmount > 0 && switchesAmount < channel) {
|
||||
return { error, msg: 'Device channel does not exist' };
|
||||
return { error: 404, msg: errors.ch404 };
|
||||
}
|
||||
|
||||
if (error || (!status && !switches)) {
|
||||
if (error && parseInt(error) === 401) {
|
||||
return device;
|
||||
}
|
||||
return { error, msg: 'Device does not exist' };
|
||||
return { error, msg: errors[error] };
|
||||
}
|
||||
|
||||
let stateToSwitch = state;
|
||||
@@ -66,21 +62,25 @@ const setDevicePowerState = {
|
||||
});
|
||||
}
|
||||
|
||||
const actionParams = {
|
||||
apiUrl: this.getApiWebSocket(),
|
||||
at: this.at,
|
||||
apiKey: this.apiKey,
|
||||
deviceId,
|
||||
params,
|
||||
state: stateToSwitch,
|
||||
};
|
||||
const response = await this.makeRequest({
|
||||
method: 'post',
|
||||
uri: '/user/device/status',
|
||||
body: {
|
||||
deviceid: deviceId,
|
||||
params,
|
||||
appid: APP_ID,
|
||||
nonce,
|
||||
ts: timestamp,
|
||||
version: 8,
|
||||
},
|
||||
});
|
||||
|
||||
if (this.apiKey !== deviceApiKey) {
|
||||
actionParams.apiKey = deviceApiKey;
|
||||
const responseError = _get(response, 'error', false);
|
||||
|
||||
if (responseError) {
|
||||
return { error: responseError, msg: errors[responseError] };
|
||||
}
|
||||
|
||||
return ChangeState.set(actionParams);
|
||||
return { status: 'ok', state, channel };
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = setDevicePowerState;
|
||||
@@ -1,4 +1,4 @@
|
||||
const toggleDeviceMixin = {
|
||||
module.exports = {
|
||||
/**
|
||||
* Toggle power state for a specific device
|
||||
*
|
||||
@@ -11,5 +11,3 @@ const toggleDeviceMixin = {
|
||||
return this.setDevicePowerState(deviceId, 'toggle', channel);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = toggleDeviceMixin;
|
||||
@@ -1,15 +1,16 @@
|
||||
const { _get } = require('../../lib/helpers');
|
||||
const { _get } = require('../helpers/utilities');
|
||||
const errors = require('../data/errors');
|
||||
|
||||
const firmwareUpdate = devicesList =>
|
||||
const parseFirmwareUpdates = devicesList =>
|
||||
devicesList.map(device => {
|
||||
const model = _get(device, 'extra.extra.model', false);
|
||||
const fwVersion = _get(device, 'params.fwVersion', false);
|
||||
|
||||
if (!model || !fwVersion) {
|
||||
return { error: 500, msg: "Can't get model or firmware version" };
|
||||
return { error: 500, msg: errors.noFirmware };
|
||||
}
|
||||
|
||||
return { model, version: fwVersion, deviceid: device.deviceid };
|
||||
});
|
||||
|
||||
module.exports = firmwareUpdate;
|
||||
module.exports = parseFirmwareUpdates;
|
||||
38
src/parsers/parsePowerUsage.js
Normal file
38
src/parsers/parsePowerUsage.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Return daily power usage
|
||||
*
|
||||
* @param hundredDaysKwhData
|
||||
*
|
||||
* @returns {{daily: *, monthly: *}}
|
||||
*/
|
||||
const parsePowerUsage = ({ hundredDaysKwhData }) => {
|
||||
const today = new Date();
|
||||
const days = today.getDate();
|
||||
|
||||
let monthlyUsage = 0;
|
||||
const dailyUsage = [];
|
||||
|
||||
for (let day = 0; day < days; day += 1) {
|
||||
const s = hundredDaysKwhData.substr(6 * day, 2);
|
||||
const c = hundredDaysKwhData.substr(6 * day + 2, 2);
|
||||
const f = hundredDaysKwhData.substr(6 * day + 4, 2);
|
||||
const h = parseInt(s, 16);
|
||||
const y = parseInt(c, 16);
|
||||
const I = parseInt(f, 16);
|
||||
const E = parseFloat(`${h}.${y}${I}`);
|
||||
|
||||
dailyUsage.push({
|
||||
day: days - day,
|
||||
usage: E,
|
||||
});
|
||||
|
||||
monthlyUsage += E;
|
||||
}
|
||||
|
||||
return {
|
||||
monthly: monthlyUsage,
|
||||
daily: dailyUsage,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = parsePowerUsage;
|
||||
14
src/payloads/credentialsPayload.js
Normal file
14
src/payloads/credentialsPayload.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { APP_ID } = require('../data/constants');
|
||||
const { timestamp, nonce } = require('../helpers/utilities');
|
||||
|
||||
const credentialsPayload = ({ email, phoneNumber, password }) => ({
|
||||
appid: APP_ID,
|
||||
email,
|
||||
phoneNumber,
|
||||
password,
|
||||
ts: timestamp,
|
||||
version: 8,
|
||||
nonce,
|
||||
});
|
||||
|
||||
module.exports = credentialsPayload;
|
||||
13
src/payloads/deviceStatus.js
Normal file
13
src/payloads/deviceStatus.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const { APP_ID } = require('../data/constants');
|
||||
const { timestamp, nonce } = require('../helpers/utilities');
|
||||
|
||||
const deviceStatus = ({ deviceId, params }) => ({
|
||||
deviceid: deviceId,
|
||||
appid: APP_ID,
|
||||
nonce,
|
||||
ts: timestamp,
|
||||
version: 8,
|
||||
params,
|
||||
});
|
||||
|
||||
module.exports = deviceStatus;
|
||||
20
src/payloads/wssLoginPayload.js
Normal file
20
src/payloads/wssLoginPayload.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const { APP_ID } = require('../data/constants');
|
||||
const { timestamp, nonce } = require('../helpers/utilities');
|
||||
|
||||
const wssLoginPayload = ({ at, apiKey }) => {
|
||||
const payload = {
|
||||
action: 'userOnline',
|
||||
at,
|
||||
apikey: apiKey,
|
||||
appid: APP_ID,
|
||||
nonce,
|
||||
ts: timestamp,
|
||||
userAgent: 'ewelink-api',
|
||||
sequence: Math.floor(timestamp * 1000),
|
||||
version: 8,
|
||||
};
|
||||
|
||||
return JSON.stringify(payload);
|
||||
};
|
||||
|
||||
module.exports = wssLoginPayload;
|
||||
@@ -1,14 +1,15 @@
|
||||
const { timestamp } = require('../helpers/utilities');
|
||||
|
||||
const wssUpdatePayload = ({ apiKey, deviceId, params }) => {
|
||||
const timeStamp = new Date() / 1000;
|
||||
const sequence = Math.floor(timeStamp * 1000);
|
||||
const payload = {
|
||||
action: 'update',
|
||||
deviceid: `${deviceId}`,
|
||||
apikey: apiKey,
|
||||
deviceid: deviceId,
|
||||
selfApikey: apiKey,
|
||||
params,
|
||||
sequence,
|
||||
userAgent: 'app',
|
||||
ts: timestamp,
|
||||
userAgent: 'ewelink-api',
|
||||
sequence: Math.floor(timestamp * 1000),
|
||||
};
|
||||
return JSON.stringify(payload);
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
const { encryptationData } = require('../ewelink-helper');
|
||||
const { encryptationData } = require('../helpers/ewelink');
|
||||
const { timestamp } = require('../helpers/utilities');
|
||||
|
||||
const zeroConfUpdatePayload = (selfApikey, deviceId, deviceKey, params) => {
|
||||
const encryptedData = encryptationData(JSON.stringify(params), deviceKey);
|
||||
const timeStamp = new Date() / 1000;
|
||||
const sequence = Math.floor(timeStamp * 1000);
|
||||
|
||||
return {
|
||||
sequence: sequence.toString(),
|
||||
sequence: Math.floor(timestamp * 1000).toString(),
|
||||
deviceid: deviceId,
|
||||
selfApikey,
|
||||
iv: encryptedData.iv,
|
||||
3
test/_setup/cache/.gitignore
vendored
Normal file
3
test/_setup/cache/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# this folder contains cache files used on ZeroConf test cases
|
||||
*
|
||||
!.gitignore
|
||||
1
test/_setup/config/.gitignore
vendored
Normal file
1
test/_setup/config/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
credentials.js
|
||||
@@ -12,7 +12,6 @@ const allDevicesExpectations = {
|
||||
showBrand: expect.any(Boolean),
|
||||
extra: {
|
||||
extra: {
|
||||
uiid: expect.any(Number),
|
||||
model: expect.any(String),
|
||||
},
|
||||
},
|
||||
@@ -25,7 +24,6 @@ const specificDeviceExpectations = {
|
||||
online: expect.any(Boolean),
|
||||
extra: {
|
||||
extra: {
|
||||
uiid: expect.any(Number),
|
||||
model: expect.any(String),
|
||||
},
|
||||
},
|
||||
|
||||
61
test/_setup/nock-helpers.js
Normal file
61
test/_setup/nock-helpers.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const nock = require('nock');
|
||||
const { writeFile } = require('fs');
|
||||
|
||||
exports.startNockRecording = () => {
|
||||
nock.recorder.rec({
|
||||
dont_print: true,
|
||||
enable_reqheaders_recording: false,
|
||||
output_objects: true,
|
||||
});
|
||||
};
|
||||
|
||||
exports.storeNockRecordings = pathToTape => {
|
||||
const nockCallObjects = nock.recorder.play();
|
||||
writeFile(pathToTape, JSON.stringify(nockCallObjects, null, 2), () => {});
|
||||
};
|
||||
|
||||
exports.playbackNockTapes = pathToTape => {
|
||||
const nocks = nock.load(pathToTape);
|
||||
|
||||
nocks.forEach(function(n) {
|
||||
n.filteringPath(path => filteringPath(n, path));
|
||||
n.filteringRequestBody((b, rb) => filteringRequestBody(b, rb));
|
||||
});
|
||||
|
||||
nock.recorder.play();
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace dynamic values on request body using recorded data
|
||||
*/
|
||||
const filteringRequestBody = (body, recordedBody) => {
|
||||
if (typeof body !== 'string' || typeof recordedBody !== 'object') {
|
||||
return body;
|
||||
}
|
||||
const jsonBody = JSON.parse(body);
|
||||
jsonBody.ts = recordedBody.ts;
|
||||
jsonBody.nonce = recordedBody.nonce;
|
||||
return JSON.stringify(jsonBody);
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace dynamic values on request URLs using recorded data
|
||||
*/
|
||||
const filteringPath = (n, path) => {
|
||||
const regexTimestampInPath = /(?<=ts=)[^&]*/g;
|
||||
const timestampInPath = path.match(regexTimestampInPath);
|
||||
|
||||
if (!timestampInPath) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const regexTimestampInRecordedPath = /ts=[^&]*/g;
|
||||
const recordedPath = n.interceptors[0].uri;
|
||||
const timestampInRecordedPath = recordedPath.match(regexTimestampInPath);
|
||||
const updatedPath = recordedPath.replace(
|
||||
regexTimestampInRecordedPath,
|
||||
`ts=${timestampInRecordedPath[0]}`
|
||||
);
|
||||
|
||||
return updatedPath;
|
||||
};
|
||||
59
test/_setup/setupTests.js
Normal file
59
test/_setup/setupTests.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const path = require('path');
|
||||
const delay = require('delay');
|
||||
|
||||
const {
|
||||
startNockRecording,
|
||||
storeNockRecordings,
|
||||
playbackNockTapes,
|
||||
} = require('./nock-helpers');
|
||||
|
||||
// Change to 'record' or 'play' to use nock on tests
|
||||
// Set to false to run live against eWeLink servers
|
||||
const nockAction = false;
|
||||
|
||||
// These files needs a cooldown delay between tests
|
||||
const testsToDelay = [
|
||||
'env-node.spec.js',
|
||||
'env-serverless.spec.js',
|
||||
'invalid-credentials.spec.js',
|
||||
'power-usage.spec.js',
|
||||
'temperature-humidity.spec.js',
|
||||
'valid-credentials.spec.js',
|
||||
];
|
||||
|
||||
const getTapFilename = global => {
|
||||
const { testPath } = global.jasmine;
|
||||
const tapeFile = path.basename(testPath, '.js');
|
||||
return `./test/_setup/tapes/${tapeFile}.json`;
|
||||
};
|
||||
|
||||
global.beforeAll(() => {
|
||||
if (nockAction === 'record') {
|
||||
startNockRecording();
|
||||
}
|
||||
|
||||
if (nockAction === 'play') {
|
||||
const tapeFile = getTapFilename(global);
|
||||
playbackNockTapes(tapeFile);
|
||||
}
|
||||
});
|
||||
|
||||
global.afterAll(() => {
|
||||
if (nockAction === 'record') {
|
||||
const tapeFile = getTapFilename(global);
|
||||
storeNockRecordings(tapeFile);
|
||||
}
|
||||
});
|
||||
|
||||
global.beforeEach(async () => {
|
||||
if (nockAction === 'play') {
|
||||
return;
|
||||
}
|
||||
|
||||
const { testPath } = global.jasmine;
|
||||
const testFile = path.basename(testPath);
|
||||
|
||||
if (testsToDelay.includes(testFile)) {
|
||||
await delay(1000);
|
||||
}
|
||||
});
|
||||
3
test/_setup/tapes/.gitignore
vendored
Normal file
3
test/_setup/tapes/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# this folder will store "tape" files for nock
|
||||
*
|
||||
!.gitignore
|
||||
156
test/class-instantiation.spec.js
Normal file
156
test/class-instantiation.spec.js
Normal file
@@ -0,0 +1,156 @@
|
||||
const ewelink = require('../main');
|
||||
const errors = require('../src/data/errors');
|
||||
|
||||
describe('main class instantiation: valid parameters combinations', () => {
|
||||
test('email and password should initialize class', async () => {
|
||||
const credentials = { email: 'user@email.com', password: 'pass' };
|
||||
const connection = new ewelink(credentials);
|
||||
expect(connection).toEqual({
|
||||
region: 'us',
|
||||
email: credentials.email,
|
||||
phoneNumber: null,
|
||||
password: credentials.password,
|
||||
apiKey: null,
|
||||
arpTable: null,
|
||||
at: null,
|
||||
devicesCache: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('email and password with region should initialize class', async () => {
|
||||
const credentials = {
|
||||
region: 'cn',
|
||||
email: 'user@email.com',
|
||||
password: 'pass',
|
||||
};
|
||||
const connection = new ewelink(credentials);
|
||||
expect(connection).toEqual({
|
||||
region: 'cn',
|
||||
email: credentials.email,
|
||||
phoneNumber: null,
|
||||
password: credentials.password,
|
||||
apiKey: null,
|
||||
arpTable: null,
|
||||
at: null,
|
||||
devicesCache: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('phone number and password should initialize class', async () => {
|
||||
const credentials = { phoneNumber: '555123789', password: 'pass' };
|
||||
const connection = new ewelink(credentials);
|
||||
expect(connection).toEqual({
|
||||
region: 'us',
|
||||
email: null,
|
||||
phoneNumber: credentials.phoneNumber,
|
||||
password: credentials.password,
|
||||
apiKey: null,
|
||||
at: null,
|
||||
arpTable: null,
|
||||
devicesCache: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('access token should initialize class', async () => {
|
||||
const credentials = { at: 'xxxyyyzzz' };
|
||||
const connection = new ewelink(credentials);
|
||||
expect(connection).toEqual({
|
||||
region: 'us',
|
||||
email: null,
|
||||
phoneNumber: null,
|
||||
password: null,
|
||||
apiKey: null,
|
||||
at: credentials.at,
|
||||
arpTable: null,
|
||||
devicesCache: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('devices and arp table cache files should initialize class', async () => {
|
||||
const credentials = { devicesCache: 'devices', arpTable: 'arptable' };
|
||||
const connection = new ewelink(credentials);
|
||||
expect(connection).toEqual({
|
||||
region: 'us',
|
||||
email: null,
|
||||
phoneNumber: null,
|
||||
password: null,
|
||||
apiKey: null,
|
||||
at: null,
|
||||
arpTable: credentials.arpTable,
|
||||
devicesCache: credentials.devicesCache,
|
||||
});
|
||||
});
|
||||
|
||||
test('email and access token should initialize class', async () => {
|
||||
const credentials = {
|
||||
email: 'user@email.com',
|
||||
at: 'xxxyyyzzz',
|
||||
};
|
||||
const connection = new ewelink(credentials);
|
||||
expect(connection).toEqual({
|
||||
region: 'us',
|
||||
email: credentials.email,
|
||||
phoneNumber: null,
|
||||
password: null,
|
||||
apiKey: null,
|
||||
at: credentials.at,
|
||||
arpTable: null,
|
||||
devicesCache: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('main class instantiation: invalid parameters combinations', () => {
|
||||
test('user and no password should fail', async () => {
|
||||
const credentials = { email: 'user@email.com' };
|
||||
expect(() => {
|
||||
const connection = new ewelink(credentials);
|
||||
}).toThrow(errors.invalidCredentials);
|
||||
});
|
||||
|
||||
test('only password should fail', async () => {
|
||||
const credentials = { password: 'pass' };
|
||||
expect(() => {
|
||||
const connection = new ewelink(credentials);
|
||||
}).toThrow(errors.invalidCredentials);
|
||||
});
|
||||
|
||||
test('phone number and no password should fail', async () => {
|
||||
const credentials = { phoneNumber: '555123789' };
|
||||
expect(() => {
|
||||
const connection = new ewelink(credentials);
|
||||
}).toThrow(errors.invalidCredentials);
|
||||
});
|
||||
|
||||
test('email and phone number should fail', async () => {
|
||||
const credentials = { email: 'user@email.com', phoneNumber: '555123789' };
|
||||
expect(() => {
|
||||
const connection = new ewelink(credentials);
|
||||
}).toThrow(errors.invalidCredentials);
|
||||
});
|
||||
|
||||
test('email and phone number with password should fail', async () => {
|
||||
const credentials = {
|
||||
email: 'user@email.com',
|
||||
phoneNumber: '555123789',
|
||||
password: 'pass',
|
||||
};
|
||||
expect(() => {
|
||||
const connection = new ewelink(credentials);
|
||||
}).toThrow(errors.invalidCredentials);
|
||||
});
|
||||
|
||||
test('devices cache without arp table should fail', async () => {
|
||||
const credentials = { devicesCache: 'devices' };
|
||||
expect(() => {
|
||||
const connection = new ewelink(credentials);
|
||||
}).toThrow(errors.invalidCredentials);
|
||||
});
|
||||
|
||||
test('arp table without devices cache should fail', async () => {
|
||||
const credentials = { arpTable: 'arptable' };
|
||||
expect(() => {
|
||||
const connection = new ewelink(credentials);
|
||||
}).toThrow(errors.invalidCredentials);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,3 @@
|
||||
const delay = require('delay');
|
||||
|
||||
const ewelink = require('../main');
|
||||
|
||||
const {
|
||||
@@ -7,7 +5,7 @@ const {
|
||||
password,
|
||||
singleChannelDeviceId,
|
||||
fourChannelsDevice,
|
||||
} = require('./_setup/credentials.js');
|
||||
} = require('./_setup/config/credentials.js');
|
||||
|
||||
const {
|
||||
credentialsExpectations,
|
||||
@@ -22,10 +20,6 @@ describe('env: node script', () => {
|
||||
conn = new ewelink({ email, password });
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await delay(1000);
|
||||
});
|
||||
|
||||
test('get ewelink credentials', async () => {
|
||||
const credentials = await conn.getCredentials();
|
||||
expect(typeof credentials).toBe('object');
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
const delay = require('delay');
|
||||
|
||||
const ewelink = require('../main');
|
||||
|
||||
const {
|
||||
@@ -7,7 +5,7 @@ const {
|
||||
password,
|
||||
singleChannelDeviceId,
|
||||
fourChannelsDevice,
|
||||
} = require('./_setup/credentials.js');
|
||||
} = require('./_setup/config/credentials.js');
|
||||
|
||||
const {
|
||||
credentialsExpectations,
|
||||
@@ -19,10 +17,6 @@ describe('env: serverless', () => {
|
||||
let accessToken;
|
||||
let apiKey;
|
||||
|
||||
beforeEach(async () => {
|
||||
await delay(1000);
|
||||
});
|
||||
|
||||
test('get ewelink credentials', async () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const credentials = await conn.getCredentials();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const ewelink = require('../main');
|
||||
const errors = require('../src/data/errors');
|
||||
|
||||
const {
|
||||
email,
|
||||
@@ -6,11 +7,11 @@ const {
|
||||
singleChannelDeviceId,
|
||||
outdatedFirmwareDevice,
|
||||
updatedFirmwareDevice,
|
||||
} = require('./_setup/credentials.js');
|
||||
} = require('./_setup/config/credentials.js');
|
||||
|
||||
const { firmwareExpectations } = require('./_setup/expectations');
|
||||
|
||||
describe.skip('firmware: get version methods', () => {
|
||||
describe('firmware: get version methods', () => {
|
||||
let connection;
|
||||
|
||||
beforeAll(() => {
|
||||
@@ -42,27 +43,27 @@ describe.skip('firmware: get version methods', () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const firmwareVersion = await conn.getFirmwareVersion('invalid deviceid');
|
||||
expect(typeof firmwareVersion).toBe('object');
|
||||
expect(firmwareVersion.msg).toBe('Device does not exist');
|
||||
expect(firmwareVersion.error).toBe(500);
|
||||
expect(firmwareVersion.msg).toBe(errors['404']);
|
||||
expect(firmwareVersion.error).toBe(404);
|
||||
});
|
||||
|
||||
test('get device firmware version using invalid credentials should fail', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const firmware = await conn.getFirmwareVersion(singleChannelDeviceId);
|
||||
expect(typeof firmware).toBe('object');
|
||||
expect(firmware.msg).toBe('Authentication error');
|
||||
expect(firmware.error).toBe(401);
|
||||
expect(firmware.msg).toBe(errors['406']);
|
||||
expect(firmware.error).toBe(406);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('firmware: check updates methods', () => {
|
||||
describe('firmware: check updates methods', () => {
|
||||
let connection;
|
||||
|
||||
beforeAll(() => {
|
||||
connection = new ewelink({ email, password });
|
||||
});
|
||||
|
||||
test.skip('outdated device firmware should return available version', async () => {
|
||||
test('outdated device firmware should return available version', async () => {
|
||||
const status = await connection.checkDeviceUpdate(outdatedFirmwareDevice);
|
||||
expect(typeof status).toBe('object');
|
||||
expect(typeof status).toBe('object');
|
||||
@@ -82,7 +83,8 @@ describe.skip('firmware: check updates methods', () => {
|
||||
test('invalid device update check should return error', async () => {
|
||||
const status = await connection.checkDeviceUpdate('invalid deviceid');
|
||||
expect(typeof status).toBe('object');
|
||||
expect(status.error).toBe(500);
|
||||
expect(status.msg).toBe(errors['404']);
|
||||
expect(status.error).toBe(404);
|
||||
});
|
||||
|
||||
test('get devices update check should be valid response', async () => {
|
||||
@@ -95,7 +97,7 @@ describe.skip('firmware: check updates methods', () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const status = await conn.checkDevicesUpdates();
|
||||
expect(typeof status).toBe('object');
|
||||
expect(status.msg).toBe('Authentication error');
|
||||
expect(status.error).toBe(401);
|
||||
expect(status.msg).toBe(errors['406']);
|
||||
expect(status.error).toBe(406);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
const ewelinkHelpers = require('../lib/ewelink-helper');
|
||||
const ewelinkHelpers = require('../src/helpers/ewelink');
|
||||
|
||||
describe('check eWeLink helpers', () => {
|
||||
test('should return fake imei', async () => {
|
||||
const imei = ewelinkHelpers.makeFakeIMEI();
|
||||
expect(imei.length).toBe(36);
|
||||
expect(imei.substr(0, 9)).toBe('DF7425A0-');
|
||||
expect(imei.substr(imei.length - 18, imei.length)).toBe(
|
||||
'-9F5E-3BC9179E48FB'
|
||||
);
|
||||
});
|
||||
|
||||
test('make authorization sign should return right string', async () => {
|
||||
const auth = ewelinkHelpers.makeAuthorizationSign({ data: 'string' });
|
||||
expect(auth.length).toBe(44);
|
||||
expect(auth).toBe('WtmdvaPxqhi3pd8ck1R/bvzfRzHgxDTwgnuOib33xx4=');
|
||||
expect(auth).toBe('7Aaa/8EuRScACNrZTATW2WKIY7lcRnjgWHTiBl8G0TQ=');
|
||||
});
|
||||
|
||||
test('getDeviceChannelCount method should return right value', async () => {
|
||||
|
||||
@@ -1,54 +1,51 @@
|
||||
const delay = require('delay');
|
||||
|
||||
const ewelink = require('../main');
|
||||
const errors = require('../src/data/errors');
|
||||
|
||||
const {
|
||||
singleChannelDeviceId,
|
||||
deviceIdWithPower,
|
||||
fourChannelsDevice,
|
||||
} = require('./_setup/credentials.js');
|
||||
} = require('./_setup/config/credentials.js');
|
||||
|
||||
describe('invalid credentials', () => {
|
||||
beforeEach(async () => {
|
||||
await delay(1000);
|
||||
});
|
||||
|
||||
test('no credentials given', async () => {
|
||||
const conn = new ewelink({});
|
||||
expect(typeof conn).toBe('object');
|
||||
expect(conn.error).toBe('No credentials provided');
|
||||
expect(() => {
|
||||
const conn = new ewelink({});
|
||||
}).toThrow(errors.invalidCredentials);
|
||||
});
|
||||
|
||||
test('get error response on ewelink credentials', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const credentials = await conn.getCredentials();
|
||||
expect(typeof credentials).toBe('object');
|
||||
expect(credentials.msg).toBe('Authentication error');
|
||||
expect(credentials.error).toBe(400);
|
||||
expect(credentials.msg).toBe(errors[406]);
|
||||
expect(credentials.error).toBe(406);
|
||||
});
|
||||
|
||||
test('get error response on all devices', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const devices = await conn.getDevices();
|
||||
expect(typeof devices).toBe('object');
|
||||
expect(devices.msg).toBe('Authentication error');
|
||||
expect(devices.error).toBe(401);
|
||||
expect(devices.msg).toBe(errors['406']);
|
||||
expect(devices.error).toBe(406);
|
||||
});
|
||||
|
||||
test('get error response on specific device', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const device = await conn.getDevice(singleChannelDeviceId);
|
||||
const { msg, error } = device;
|
||||
expect(typeof device).toBe('object');
|
||||
expect(device.msg).toBe('Authentication error');
|
||||
expect(device.error).toBe(401);
|
||||
expect(msg).toBe(errors[406]);
|
||||
expect(error).toBe(406);
|
||||
});
|
||||
|
||||
test('get device power state should fail', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const powerState = await conn.getDevicePowerState(singleChannelDeviceId);
|
||||
const { msg, error } = powerState;
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Authentication error');
|
||||
expect(powerState.error).toBe(401);
|
||||
expect(msg).toBe(errors[401]);
|
||||
expect(error).toBe(401);
|
||||
});
|
||||
|
||||
test('set device power state should fail', async () => {
|
||||
@@ -58,17 +55,17 @@ describe('invalid credentials', () => {
|
||||
singleChannelDeviceId,
|
||||
'on'
|
||||
);
|
||||
const { msg, error } = powerState;
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Authentication error');
|
||||
expect(powerState.error).toBe(401);
|
||||
expect(msg).toBe(errors[406]);
|
||||
expect(error).toBe(406);
|
||||
});
|
||||
|
||||
test('current month power usage should fail', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const powerUsage = await conn.getDevicePowerUsage(deviceIdWithPower);
|
||||
expect(typeof powerUsage).toBe('object');
|
||||
expect(powerUsage.msg).toBe('Forbidden');
|
||||
expect(powerUsage.error).toBe(403);
|
||||
expect(powerUsage.error).toBe(errors.noPower);
|
||||
});
|
||||
|
||||
test('get channel count 1 should fail', async () => {
|
||||
@@ -76,16 +73,18 @@ describe('invalid credentials', () => {
|
||||
const switchesAmount = await conn.getDeviceChannelCount(
|
||||
singleChannelDeviceId
|
||||
);
|
||||
const { msg, error } = switchesAmount;
|
||||
expect(typeof switchesAmount).toBe('object');
|
||||
expect(switchesAmount.msg).toBe('Authentication error');
|
||||
expect(switchesAmount.error).toBe(401);
|
||||
expect(msg).toBe(errors[406]);
|
||||
expect(error).toBe(406);
|
||||
});
|
||||
|
||||
test('get channel count 4 should fail', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const switchesAmount = await conn.getDeviceChannelCount(fourChannelsDevice);
|
||||
const { msg, error } = switchesAmount;
|
||||
expect(typeof switchesAmount).toBe('object');
|
||||
expect(switchesAmount.msg).toBe('Authentication error');
|
||||
expect(switchesAmount.error).toBe(401);
|
||||
expect(msg).toBe(errors[406]);
|
||||
expect(error).toBe(406);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
const delay = require('delay');
|
||||
|
||||
const ewelink = require('../main');
|
||||
|
||||
const {
|
||||
email,
|
||||
password,
|
||||
deviceIdWithPower,
|
||||
} = require('./_setup/credentials.js');
|
||||
} = require('./_setup/config/credentials.js');
|
||||
|
||||
const {
|
||||
rawPowerUsageExpectations,
|
||||
@@ -21,13 +19,9 @@ describe('power usage: node script', () => {
|
||||
await conn.getCredentials();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await delay(1000);
|
||||
});
|
||||
|
||||
test('should return raw power usage', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const powerUsage = await conn.getDeviceRawPowerUsage(deviceIdWithPower);
|
||||
const powerUsage = await conn.getDevicePowerUsageRaw(deviceIdWithPower);
|
||||
expect(typeof powerUsage).toBe('object');
|
||||
expect(powerUsage).toMatchObject(rawPowerUsageExpectations);
|
||||
expect(powerUsage.data.hundredDaysKwhData.length).toBe(600);
|
||||
@@ -57,7 +51,7 @@ describe('power usage: serverless', () => {
|
||||
test('should return raw power usage', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const conn = new ewelink({ at: accessToken, apiKey });
|
||||
const powerUsage = await conn.getDeviceRawPowerUsage(deviceIdWithPower);
|
||||
const powerUsage = await conn.getDevicePowerUsageRaw(deviceIdWithPower);
|
||||
expect(typeof powerUsage).toBe('object');
|
||||
expect(powerUsage).toMatchObject(rawPowerUsageExpectations);
|
||||
expect(powerUsage.data.hundredDaysKwhData.length).toBe(600);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
const delay = require('delay');
|
||||
|
||||
const ewelink = require('../main');
|
||||
const errors = require('../src/data/errors');
|
||||
|
||||
const {
|
||||
email,
|
||||
password,
|
||||
deviceIdWithoutTempAndHum,
|
||||
deviceIdWithTempAndHum: thDevice,
|
||||
} = require('./_setup/credentials.js');
|
||||
} = require('./_setup/config/credentials.js');
|
||||
|
||||
describe('current temperature and humidity: node script', () => {
|
||||
let conn;
|
||||
@@ -19,7 +18,6 @@ describe('current temperature and humidity: node script', () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await delay(1000);
|
||||
device = await conn.getDevice(thDevice);
|
||||
});
|
||||
|
||||
@@ -63,7 +61,6 @@ describe('current temperature and humidity: serverless', () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await delay(1000);
|
||||
connSL = new ewelink({ at: accessToken, apiKey });
|
||||
device = await connSL.getDevice(thDevice);
|
||||
});
|
||||
@@ -99,16 +96,16 @@ describe('current temperature and humidity: invalid device', () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const temperature = await conn.getDeviceCurrentTemperature('invalid');
|
||||
expect(typeof temperature).toBe('object');
|
||||
expect(temperature.msg).toBe('Device does not exist');
|
||||
expect(temperature.error).toBe(500);
|
||||
expect(temperature.msg).toBe(errors['404']);
|
||||
expect(temperature.error).toBe(404);
|
||||
});
|
||||
|
||||
test('get device current humidity should fail', async () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const humidity = await conn.getDeviceCurrentHumidity('invalid');
|
||||
expect(typeof humidity).toBe('object');
|
||||
expect(humidity.msg).toBe('Device does not exist');
|
||||
expect(humidity.error).toBe(500);
|
||||
expect(humidity.msg).toBe(errors['404']);
|
||||
expect(humidity.error).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -119,8 +116,8 @@ describe('current temperature and humidity: device without sensor', () => {
|
||||
deviceIdWithoutTempAndHum
|
||||
);
|
||||
expect(typeof temperature).toBe('object');
|
||||
expect(temperature.msg).toBe("Can't read sensor data from device");
|
||||
expect(temperature.error).toBe(500);
|
||||
expect(temperature.msg).toBe(errors.noSensor);
|
||||
expect(temperature.error).toBe(404);
|
||||
});
|
||||
|
||||
test('get device current humidity should fail', async () => {
|
||||
@@ -129,8 +126,8 @@ describe('current temperature and humidity: device without sensor', () => {
|
||||
deviceIdWithoutTempAndHum
|
||||
);
|
||||
expect(typeof humidity).toBe('object');
|
||||
expect(humidity.msg).toBe("Can't read sensor data from device");
|
||||
expect(humidity.error).toBe(500);
|
||||
expect(humidity.msg).toBe(errors.noSensor);
|
||||
expect(humidity.error).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -139,15 +136,15 @@ describe('current temperature and humidity: invalid credentials', () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const result = await conn.getDeviceCurrentTemperature(thDevice);
|
||||
expect(typeof result).toBe('object');
|
||||
expect(result.msg).toBe('Authentication error');
|
||||
expect(result.error).toBe(401);
|
||||
expect(result.msg).toBe(errors['406']);
|
||||
expect(result.error).toBe(406);
|
||||
});
|
||||
|
||||
test('get device current humidity should fail', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const result = await conn.getDeviceCurrentHumidity(thDevice);
|
||||
expect(typeof result).toBe('object');
|
||||
expect(result.msg).toBe('Authentication error');
|
||||
expect(result.error).toBe(401);
|
||||
expect(result.msg).toBe(errors['406']);
|
||||
expect(result.error).toBe(406);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const ewelink = require('../main');
|
||||
const errors = require('../src/data/errors');
|
||||
|
||||
const { email, password, region } = require('./_setup/credentials.js');
|
||||
const { email, password, region } = require('./_setup/config/credentials.js');
|
||||
|
||||
const { regionExpectations } = require('./_setup/expectations');
|
||||
|
||||
@@ -21,17 +22,15 @@ describe('check user information', () => {
|
||||
});
|
||||
const response = await connection.getRegion();
|
||||
expect(typeof response).toBe('object');
|
||||
expect(response.msg).toBe('Authentication error');
|
||||
expect(response.error).toBe(400);
|
||||
expect(response.msg).toBe(errors['406']);
|
||||
expect(response.error).toBe(406);
|
||||
});
|
||||
|
||||
test('invalid initialization should warn user', async () => {
|
||||
const connection = new ewelink({ at: 'access token' });
|
||||
const response = await connection.getRegion();
|
||||
expect(typeof response).toBe('object');
|
||||
expect(response.msg).toBe(
|
||||
'Library needs to be initialized using email and password'
|
||||
);
|
||||
expect(response.msg).toBe(errors.invalidAuth);
|
||||
expect(response.error).toBe(406);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,35 +1,30 @@
|
||||
const delay = require('delay');
|
||||
|
||||
const ewelink = require('../main');
|
||||
const errors = require('../src/data/errors');
|
||||
|
||||
const {
|
||||
email,
|
||||
password,
|
||||
deviceIdWithoutPower,
|
||||
fourChannelsDevice,
|
||||
} = require('./_setup/credentials.js');
|
||||
} = require('./_setup/config/credentials.js');
|
||||
|
||||
const { credentialsExpectations } = require('./_setup/expectations');
|
||||
|
||||
describe('valid credentials, invalid device', () => {
|
||||
beforeEach(async () => {
|
||||
await delay(1000);
|
||||
});
|
||||
|
||||
test('get power state on invalid device should fail', async () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const powerState = await conn.getDevicePowerState('invalid deviceid');
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Device does not exist');
|
||||
expect(powerState.error).toBe(500);
|
||||
expect(powerState.msg).toBe(errors[404]);
|
||||
expect(powerState.error).toBe(404);
|
||||
});
|
||||
|
||||
test('get power state on wrong device channel should fail', async () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const powerState = await conn.getDevicePowerState(fourChannelsDevice, 8);
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Device channel does not exist');
|
||||
expect(powerState.error).toBe(false);
|
||||
expect(powerState.msg).toBe(errors.ch404);
|
||||
expect(powerState.error).toBe(404);
|
||||
});
|
||||
|
||||
test('set power state on invalid device should fail', async () => {
|
||||
@@ -37,8 +32,8 @@ describe('valid credentials, invalid device', () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const powerState = await conn.setDevicePowerState('invalid deviceid', 'on');
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Device does not exist');
|
||||
expect(powerState.error).toBe(500);
|
||||
expect(powerState.msg).toBe(errors[404]);
|
||||
expect(powerState.error).toBe(404);
|
||||
});
|
||||
|
||||
test('set power state on wrong device channel should fail', async () => {
|
||||
@@ -50,8 +45,8 @@ describe('valid credentials, invalid device', () => {
|
||||
8
|
||||
);
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Device channel does not exist');
|
||||
expect(powerState.error).toBe(false);
|
||||
expect(powerState.msg).toBe(errors.ch404);
|
||||
expect(powerState.error).toBe(404);
|
||||
});
|
||||
|
||||
test('toggle power state on invalid device should fail', async () => {
|
||||
@@ -59,17 +54,16 @@ describe('valid credentials, invalid device', () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const powerState = await conn.toggleDevice('invalid deviceid');
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Device does not exist');
|
||||
expect(powerState.error).toBe(500);
|
||||
expect(powerState.msg).toBe(errors[404]);
|
||||
expect(powerState.error).toBe(404);
|
||||
});
|
||||
|
||||
test('raw power usage on invalid device should fail', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const conn = new ewelink({ email, password });
|
||||
const powerUsage = await conn.getDeviceRawPowerUsage('invalid deviceid');
|
||||
const powerUsage = await conn.getDevicePowerUsageRaw('invalid deviceid');
|
||||
expect(typeof powerUsage).toBe('object');
|
||||
expect(powerUsage.msg).toBe('Forbidden');
|
||||
expect(powerUsage.error).toBe(403);
|
||||
expect(powerUsage.error).toBe(errors.noPower);
|
||||
});
|
||||
|
||||
test('current month power usage on invalid device should fail', async () => {
|
||||
@@ -77,24 +71,23 @@ describe('valid credentials, invalid device', () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const powerUsage = await conn.getDevicePowerUsage('invalid deviceid');
|
||||
expect(typeof powerUsage).toBe('object');
|
||||
expect(powerUsage.msg).toBe('Forbidden');
|
||||
expect(powerUsage.error).toBe(403);
|
||||
expect(powerUsage.error).toBe(errors.noPower);
|
||||
});
|
||||
|
||||
test('raw power on device without electricity monitor should fail', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const conn = new ewelink({ email, password });
|
||||
const powerUsage = await conn.getDeviceRawPowerUsage(deviceIdWithoutPower);
|
||||
const powerUsage = await conn.getDevicePowerUsageRaw(deviceIdWithoutPower);
|
||||
expect(typeof powerUsage).toBe('object');
|
||||
expect(powerUsage.error).toBe('No power usage data found.');
|
||||
expect(powerUsage.error).toBe(errors.noPower);
|
||||
});
|
||||
|
||||
test('get channel count should fail', async () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const switchesAmount = await conn.getDeviceChannelCount('invalid deviceid');
|
||||
expect(typeof switchesAmount).toBe('object');
|
||||
expect(switchesAmount.msg).toBe('Device does not exist');
|
||||
expect(switchesAmount.error).toBe(500);
|
||||
expect(switchesAmount.msg).toBe(errors[404]);
|
||||
expect(switchesAmount.error).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const ewelink = require('../main');
|
||||
const Zeroconf = require('../classes/Zeroconf');
|
||||
const Zeroconf = require('../src/classes/Zeroconf');
|
||||
const errors = require('../src/data/errors');
|
||||
|
||||
const {
|
||||
email,
|
||||
@@ -7,14 +8,14 @@ const {
|
||||
region,
|
||||
localIp,
|
||||
localIpInvalid,
|
||||
} = require('./_setup/credentials.js');
|
||||
} = require('./_setup/config/credentials.js');
|
||||
|
||||
const { allDevicesExpectations } = require('./_setup/expectations');
|
||||
|
||||
describe('zeroconf: save devices to cache file', () => {
|
||||
test('can save cached devices file', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const file = './test/_setup/devices-cache.json';
|
||||
const file = './test/_setup/cache/devices-cache.json';
|
||||
const conn = new ewelink({ region, email, password });
|
||||
const result = await conn.saveDevicesCache(file);
|
||||
expect(typeof result).toBe('object');
|
||||
@@ -36,15 +37,15 @@ describe('zeroconf: save devices to cache file', () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const result = await conn.saveDevicesCache(file);
|
||||
expect(typeof result).toBe('object');
|
||||
expect(result.msg).toBe('Authentication error');
|
||||
expect(result.error).toBe(401);
|
||||
expect(result.msg).toBe(errors['406']);
|
||||
expect(result.error).toBe(406);
|
||||
});
|
||||
});
|
||||
|
||||
describe('zeroconf: save arp table to file', () => {
|
||||
test('can save arp table file', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const file = './test/_setup/arp-table.json';
|
||||
const file = './test/_setup/cache/arp-table.json';
|
||||
const arpTable = await Zeroconf.saveArpTable({
|
||||
ip: localIp,
|
||||
file,
|
||||
@@ -67,7 +68,7 @@ describe('zeroconf: save arp table to file', () => {
|
||||
|
||||
test('error saving arp table file with invalid local network', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const file = './test/_setup/arp-table.json';
|
||||
const file = './test/_setup/cache/arp-table.json';
|
||||
const arpTable = await Zeroconf.saveArpTable({
|
||||
ip: localIpInvalid,
|
||||
file,
|
||||
@@ -83,7 +84,7 @@ describe('zeroconf: load devices to cache file', () => {
|
||||
const conn = new ewelink({ region, email, password });
|
||||
const devices = await conn.getDevices();
|
||||
const devicesCache = await Zeroconf.loadCachedDevices(
|
||||
'./test/_setup/devices-cache.json'
|
||||
'./test/_setup/cache/devices-cache.json'
|
||||
);
|
||||
expect(typeof devicesCache).toBe('object');
|
||||
expect(devicesCache.length).toBe(devices.length);
|
||||
@@ -101,7 +102,7 @@ describe('zeroconf: load devices to cache file', () => {
|
||||
describe('zeroconf: load arp table file', () => {
|
||||
test('can load arp table file', async () => {
|
||||
const arpTable = await Zeroconf.loadArpTable(
|
||||
'./test/_setup/arp-table.json'
|
||||
'./test/_setup/cache/arp-table.json'
|
||||
);
|
||||
expect(typeof arpTable).toBe('object');
|
||||
expect(arpTable[0]).toMatchObject({
|
||||
|
||||
Reference in New Issue
Block a user