Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v4.0.0 #113

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1109e70
use v2 API URL
skydiver Oct 21, 2020
012d0ee
getCredentials: use v2 API
skydiver Oct 21, 2020
2f4716f
getCredentials: fix getting api key and access token
skydiver Oct 21, 2020
d274a8d
getCredentials: improved error message
skydiver Oct 21, 2020
0330799
makeRequest: use v2 API
skydiver Oct 21, 2020
16772a5
makeRequest: improved error message
skydiver Oct 21, 2020
9c00d82
getDevice: use v2 API
skydiver Oct 21, 2020
94b3bc0
makeRequest: improved error message
skydiver Oct 21, 2020
47fd13f
getDevice: improved error message
skydiver Oct 21, 2020
45b1c43
DEV: update eslint + prettier + jest
skydiver Oct 21, 2020
996c678
getDevices: use v2 API
skydiver Oct 21, 2020
930cf28
getDevices: improved error message
skydiver Oct 21, 2020
2a64768
getDevicePowerState: use v2 API
skydiver Oct 21, 2020
ac80e06
setDevicePowerState: use v2 API
skydiver Oct 22, 2020
bf6ab11
toggleDevicePowerState: renamed to toggleDevicePowerState
skydiver Oct 22, 2020
e524258
getDeviceCurrentTH: improved error handling
skydiver Oct 22, 2020
ad2d72f
setDevicePowerState: improved error handling
skydiver Oct 22, 2020
894aa6f
getDeviceChannelCount: use v2 API
skydiver Oct 22, 2020
9e7bef8
getFirmwareVersion: use v2 API
skydiver Oct 22, 2020
4038c8c
checkDeviceUpdate: use v2 API
skydiver Oct 22, 2020
646dfed
checkDevicesUpdates: use v2 API
skydiver Oct 22, 2020
f2a39c3
checkDeviceUpdate: refactor
skydiver Oct 22, 2020
f81fd77
saveDevicesCache: use v2 API
skydiver Oct 22, 2020
979664d
getDeviceIP: refactor
skydiver Oct 22, 2020
08e84b5
updated dependencies
skydiver Oct 26, 2020
82f152d
openWebSocket: use v2 API
skydiver Oct 27, 2020
b3655b0
getRegion: use v2 API
skydiver Oct 27, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion main.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ class eWeLink {
* @returns {string}
*/
getApiUrl() {
return `https://${this.region}-api.coolkit.cc:8080/api`;
const domain = this.region === 'cn' ? 'cn' : 'cc';
return `https://${this.region}-apia.coolkit.${domain}`;
}

/**
Expand All @@ -95,6 +96,11 @@ class eWeLink {
return `wss://${this.region}-pconnect3.coolkit.cc:8080/api/ws`;
}

getDispatchServiceUrl() {
const domain = this.region === 'cn' ? 'cn' : 'cc';
return `https://${this.region}-dispa.coolkit.${domain}`;
}

/**
* Generate Zeroconf URL
* @param device
Expand Down
1,030 changes: 522 additions & 508 deletions package-lock.json

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,28 @@
},
"dependencies": {
"arpping": "github:skydiver/arpping",
"compare-versions": "^3.6.0",
"crypto-js": "^4.0.0",
"delay": "^4.4.0",
"node-fetch": "^2.6.1",
"random": "^2.2.0",
"websocket": "^1.0.32",
"websocket-as-promised": "^1.0.1"
"websocket-as-promised": "^1.1.0"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^7.11.0",
"eslint": "^7.12.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-prettier": "^6.12.0",
"eslint-config-wesbos": "0.0.19",
"eslint-config-prettier": "^6.14.0",
"eslint-config-wesbos": "1.0.1",
"eslint-plugin-html": "^6.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.21.4",
"eslint-plugin-react-hooks": "^4.1.2",
"jest": "^26.5.3",
"nock": "^12.0.3",
"prettier": "^1.19.1"
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^26.6.1",
"nock": "^13.0.4",
"prettier": "^2.1.2"
}
}
5 changes: 4 additions & 1 deletion src/data/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ const errors = {
403: 'Forbidden',
404: 'Device does not exist',
406: 'Authentication failed',
503: 'Service Temporarily Unavailable or Device is offline'
503: 'Service Temporarily Unavailable or Device is offline',
};

const customErrors = {
ch404: 'Device channel does not exist',
unknown: 'An unknown error occurred',
noARP: 'No ARP information found. You need to generate the ARP file.',
noDevice: 'No device found',
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",
noFirmwares: "Can't find firmware update information",
invalidAuth: 'Library needs to be initialized using email and password',
invalidCredentials: 'Invalid credentials provided',
invalidPowerState: 'Invalid power state. Expecting: "on", "off" or "toggle"',
Expand Down
38 changes: 6 additions & 32 deletions src/mixins/checkDeviceUpdate.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const { _get } = require('../helpers/utilities');
const parseFirmwareUpdates = require('../parsers/parseFirmwareUpdates');
const errors = require('../data/errors');

module.exports = {
/**
Expand All @@ -10,39 +9,14 @@ module.exports = {
* @returns {Promise<{msg: string, version: *}|{msg: string, error: number}|{msg: string, error: *}|Device|{msg: string}>}
*/
async checkDeviceUpdate(deviceId) {
const device = await this.getDevice(deviceId);
const updates = await this.checkDevicesUpdates();

const error = _get(device, 'error', false);
const update = updates.find((device) => device.deviceId === deviceId);

if (error) {
return device;
if (!update) {
throw new Error(`${errors.noFirmware}`);
}

const deviceInfoList = parseFirmwareUpdates([device]);

const deviceInfoListError = _get(deviceInfoList, 'error', false);

if (deviceInfoListError) {
return deviceInfoList;
}

const update = await this.makeRequest({
method: 'post',
url: this.getOtaUrl(),
uri: '/app',
body: { deviceInfoList },
});

const isUpdate = _get(update, 'upgradeInfoList.0.version', false);

if (!isUpdate) {
return { status: 'ok', msg: 'No update available' };
}

return {
status: 'ok',
msg: 'Update available',
version: isUpdate,
};
return update;
},
};
49 changes: 19 additions & 30 deletions src/mixins/checkDevicesUpdates.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,42 @@
const { _get } = require('../helpers/utilities');
const compareVersions = require('compare-versions');
const parseFirmwareUpdates = require('../parsers/parseFirmwareUpdates');
const errors = require('../data/errors');

module.exports = {
async checkDevicesUpdates() {
const devices = await this.getDevices();

const error = _get(devices, 'error', false);

if (error) {
return devices;
}

const deviceInfoList = parseFirmwareUpdates(devices);

const deviceInfoListError = _get(deviceInfoList, 'error', false);

if (deviceInfoListError) {
return deviceInfoList;
}

const updates = await this.makeRequest({
method: 'post',
url: this.getOtaUrl(),
uri: '/app',
uri: '/v2/device/ota/query',
body: { deviceInfoList },
});

const upgradeInfoList = _get(updates, 'upgradeInfoList', false);
const { otaInfoList } = updates;

if (!upgradeInfoList) {
return { error: "Can't find firmware update information" };
if (!otaInfoList) {
throw new Error(`${errors.noFirmwares}`);
}

return upgradeInfoList.map(device => {
const upd = _get(device, 'version', false);
/** Get current versions */
const currentVersions = {};
deviceInfoList.forEach((device) => {
currentVersions[device.deviceid] = device.version;
});

if (!upd) {
return {
status: 'ok',
deviceId: device.deviceid,
msg: 'No update available',
};
}
return otaInfoList.map((device) => {
const current = currentVersions[device.deviceid];
const { version } = device;
const outdated = compareVersions(version, current);

return {
status: 'ok',
update: !!outdated,
deviceId: device.deviceid,
msg: 'Update available',
version: upd,
msg: outdated ? 'Update available' : 'No update available',
current,
version,
};
});
},
Expand Down
40 changes: 16 additions & 24 deletions src/mixins/getCredentials.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,46 @@
const fetch = require('node-fetch');

const { _get } = require('../helpers/utilities');
const credentialsPayload = require('../payloads/credentialsPayload');
const { makeAuthorizationSign } = require('../helpers/ewelink');
const errors = require('../data/errors');

module.exports = {
/**
* Returns user credentials information
*
* @returns {Promise<{msg: string, error: *}>}
*/
async getCredentials() {
const { APP_ID, APP_SECRET } = this;

const body = credentialsPayload({
appid: APP_ID,
const body = {
countryCode: '+1',
email: this.email,
phoneNumber: this.phoneNumber,
password: this.password,
});
};

if (this.phoneNumber) {
body.phoneNumber = this.phoneNumber;
}

const request = await fetch(`${this.getApiUrl()}/user/login`, {
const request = await fetch(`${this.getApiUrl()}/v2/user/login`, {
method: 'post',
headers: {
Authorization: `Sign ${makeAuthorizationSign(APP_SECRET, body)}`,
'Content-Type': 'application/json',
'X-CK-Appid': APP_ID,
},
body: JSON.stringify(body),
});

let response = await request.json();
const 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: 406, msg: errors['406'] };
if (error) {
throw new Error(`[${error}] ${response.msg}`);
}

if (error && parseInt(error) === 301 && region) {
if (this.region !== region) {
this.region = region;
response = await this.getCredentials();
return response;
}
return { error, msg: 'Region does not exist' };
}
this.apiKey = _get(response, 'data.user.apikey', '');
this.at = _get(response, 'data.at', '');

this.apiKey = _get(response, 'user.apikey', '');
this.at = _get(response, 'at', '');
return response;
return response.data;
},
};
25 changes: 12 additions & 13 deletions src/mixins/getDevice.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { nonce, timestamp, _get } = require('../helpers/utilities');
const { _get } = require('../helpers/utilities');
const errors = require('../data/errors');

module.exports = {
Expand All @@ -10,28 +10,27 @@ module.exports = {
*/
async getDevice(deviceId) {
if (this.devicesCache) {
return this.devicesCache.find(dev => dev.deviceid === deviceId) || null;
return this.devicesCache.find((dev) => dev.deviceid === deviceId) || null;
}

const { APP_ID } = this;

const device = await this.makeRequest({
uri: `/user/device/${deviceId}`,
qs: {
deviceid: deviceId,
appid: APP_ID,
nonce,
ts: timestamp,
version: 8,
method: 'post',
uri: `/v2/device/thing/`,
body: {
thingList: [{ id: deviceId, itemType: 1 }],
},
});

const error = _get(device, 'error', false);

if (error) {
return { error, msg: errors[error] };
throw new Error(`[${error}] ${errors[error]}`);
}

if (device.thingList.length === 0) {
throw new Error(`${errors.noDevice}`);
}

return device;
return device.thingList.shift().itemData;
},
};
19 changes: 11 additions & 8 deletions src/mixins/getDeviceChannelCount.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
const { _get } = require('../helpers/utilities');
const errors = require('../data/errors');

const { getDeviceChannelCount } = require('../helpers/ewelink');

module.exports = {
/**
Expand All @@ -13,12 +10,18 @@ module.exports = {
*/
async getDeviceChannelCount(deviceId) {
const device = await this.getDevice(deviceId);
const error = _get(device, 'error', false);
const uiid = _get(device, 'extra.extra.uiid', false);
const switchesAmount = getDeviceChannelCount(uiid);

if (error) {
return { error, msg: errors[error] };
const paramSwitch = _get(device, 'params.switch', false);
const paramSwitches = _get(device, 'params.switches', false);

let switchesAmount;

if (paramSwitches) {
switchesAmount = paramSwitches.length;
}

if (!paramSwitches && paramSwitch) {
switchesAmount = 1;
}

return { status: 'ok', switchesAmount };
Expand Down
8 changes: 2 additions & 6 deletions src/mixins/getDeviceCurrentTH.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@ module.exports = {
*/
async getDeviceCurrentTH(deviceId, type = '') {
const device = await this.getDevice(deviceId);
const error = _get(device, 'error', false);

const temperature = _get(device, 'params.currentTemperature', false);
const humidity = _get(device, 'params.currentHumidity', false);

if (error) {
return device;
}

if (!temperature || !humidity) {
return { error: 404, msg: errors.noSensor };
throw new Error(`${errors.noSensor}`);
}

const data = { status: 'ok', temperature, humidity };
Expand Down
12 changes: 10 additions & 2 deletions src/mixins/getDeviceIP.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const errors = require('../data/errors');

module.exports = {
/**
* Get local IP address from a given MAC
Expand All @@ -6,10 +8,16 @@ module.exports = {
* @returns {Promise<string>}
*/
getDeviceIP(device) {
const mac = device.extra.extra.staMac;
if (!this.arpTable) {
throw new Error(errors.noARP);
}

const mac = device.extra.staMac;

const arpItem = this.arpTable.find(
item => item.mac.toLowerCase() === mac.toLowerCase()
(item) => item.mac.toLowerCase() === mac.toLowerCase()
);

return arpItem.ip;
},
};
Loading