Release v1.2.2 (#7)

* refactor websocket request

* return response from websocket request

* move request payloads to own file

* refactor: removed duplicate device request

* code linting

* JSDoc methods

* version bump
This commit is contained in:
Martin M
2019-07-23 21:30:25 -03:00
committed by GitHub
parent 513c2f660a
commit d1cf9da212
9 changed files with 193 additions and 91 deletions

View File

@@ -1,6 +1,5 @@
const crypto = require('crypto'); const crypto = require('crypto');
const random = require('random'); const random = require('random');
const nonce = require('nonce')();
const makeFakeIMEI = () => { const makeFakeIMEI = () => {
const num1 = random.int(1000, 9999); const num1 = random.int(1000, 9999);
@@ -8,55 +7,6 @@ const makeFakeIMEI = () => {
return `DF7425A0-${num1}-${num2}-9F5E-3BC9179E48FB`; return `DF7425A0-${num1}-${num2}-9F5E-3BC9179E48FB`;
}; };
const loginPayload = ({ email, password }) => ({
email,
password,
version: 6,
ts: `${Math.round(new Date().getTime() / 1000)}`,
nonce: `${nonce()}`,
appid: 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq',
imei: makeFakeIMEI(),
os: 'iOS',
model: 'iPhone10,6',
romVersion: '11.1.2',
appVersion: '3.5.3',
});
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);
};
const wssUpdatePayload = ({ apiKey, deviceId, params }) => {
const timeStamp = new Date() / 1000;
const sequence = Math.floor(timeStamp * 1000);
const payload = {
action: 'update',
userAgent: 'app',
apikey: apiKey,
deviceid: `${deviceId}`,
params,
sequence,
};
return JSON.stringify(payload);
};
const makeAuthorizationSign = body => const makeAuthorizationSign = body =>
crypto crypto
.createHmac('sha256', '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM') .createHmac('sha256', '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM')
@@ -153,8 +103,6 @@ const getDeviceChannelCount = deviceUUID => {
module.exports = { module.exports = {
makeAuthorizationSign, makeAuthorizationSign,
loginPayload, makeFakeIMEI,
wssLoginPayload,
wssUpdatePayload,
getDeviceChannelCount, getDeviceChannelCount,
}; };

9
lib/payloads/index.js Normal file
View File

@@ -0,0 +1,9 @@
const loginPayload = require('./loginPayload');
const wssLoginPayload = require('./wssLoginPayload');
const wssUpdatePayload = require('./wssUpdatePayload');
module.exports = {
loginPayload,
wssLoginPayload,
wssUpdatePayload,
};

View File

@@ -0,0 +1,19 @@
const nonce = require('nonce')();
const { makeFakeIMEI } = require('../ewelink-helper');
const loginPayload = ({ email, password }) => ({
email,
password,
version: 6,
ts: `${Math.round(new Date().getTime() / 1000)}`,
nonce: `${nonce()}`,
appid: 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq',
imei: makeFakeIMEI(),
os: 'iOS',
model: 'iPhone10,6',
romVersion: '11.1.2',
appVersion: '3.5.3',
});
module.exports = loginPayload;

View File

@@ -0,0 +1,24 @@
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;

View File

@@ -0,0 +1,15 @@
const wssUpdatePayload = ({ apiKey, deviceId, params }) => {
const timeStamp = new Date() / 1000;
const sequence = Math.floor(timeStamp * 1000);
const payload = {
action: 'update',
userAgent: 'app',
apikey: apiKey,
deviceid: `${deviceId}`,
params,
sequence,
};
return JSON.stringify(payload);
};
module.exports = wssUpdatePayload;

34
lib/websocket.js Normal file
View File

@@ -0,0 +1,34 @@
const W3CWebSocket = require('websocket').w3cwebsocket;
const WebSocketAsPromised = require('websocket-as-promised');
const delay = require('delay');
/**
* Open WebSocket connection and send provided payloads
*
* @param url
* @param payloads
* @param delayTime
*
* @returns {Array}
*/
const WebSocketRequest = async (url, payloads, ...{ delayTime = 1000 }) => {
const wsp = new WebSocketAsPromised(url, {
createWebSocket: wss => new W3CWebSocket(wss),
});
const responses = [];
wsp.onMessage.addListener(message => responses.push(message));
await wsp.open();
for (const payload of payloads) {
await wsp.send(payload);
await delay(delayTime);
}
await wsp.close();
return responses;
};
module.exports = WebSocketRequest;

125
main.js
View File

@@ -1,18 +1,15 @@
const rp = require('request-promise'); const rp = require('request-promise');
const W3CWebSocket = require('websocket').w3cwebsocket;
const WebSocketAsPromised = require('websocket-as-promised');
const delay = require('delay');
const WebSocketRequest = require('./lib/websocket');
const { _get } = require('./lib/helpers'); const { _get } = require('./lib/helpers');
const { const {
makeAuthorizationSign, makeAuthorizationSign,
loginPayload,
wssLoginPayload,
wssUpdatePayload,
getDeviceChannelCount, getDeviceChannelCount,
} = require('./lib/ewelink-helper'); } = require('./lib/ewelink-helper');
const payloads = require('./lib/payloads');
class eWeLink { class eWeLink {
constructor({ region = 'us', email, password, at, apiKey }) { constructor({ region = 'us', email, password, at, apiKey }) {
if (!at && (!email && !password)) { if (!at && (!email && !password)) {
@@ -26,14 +23,34 @@ class eWeLink {
this.apiKey = apiKey; this.apiKey = apiKey;
} }
/**
* Generate eWeLink API URL
*
* @returns {string}
*/
getApiUrl() { getApiUrl() {
return `https://${this.region}-api.coolkit.cc:8080/api`; return `https://${this.region}-api.coolkit.cc:8080/api`;
} }
/**
* Generate eWeLink WebSocket URL
*
* @returns {string}
*/
getApiWebSocket() { getApiWebSocket() {
return `wss://${this.region}-pconnect3.coolkit.cc:8080/api/ws`; return `wss://${this.region}-pconnect3.coolkit.cc:8080/api/ws`;
} }
/**
* Generate http requests helpers
*
* @param method
* @param uri
* @param body
* @param qs
*
* @returns {Promise<{msg: string, error: *}>}
*/
async makeRequest({ method = 'GET', uri, body = {}, qs = {} }) { async makeRequest({ method = 'GET', uri, body = {}, qs = {} }) {
const { at } = this; const { at } = this;
@@ -58,8 +75,13 @@ class eWeLink {
return response; return response;
} }
/**
* Helper to login into eWeLink API
*
* @returns {Promise<{msg: string, error: *}>}
*/
async login() { async login() {
const body = loginPayload({ const body = payloads.loginPayload({
email: this.email, email: this.email,
password: this.password, password: this.password,
}); });
@@ -93,28 +115,46 @@ class eWeLink {
return response; return response;
} }
/**
* Get specific device information
*
* @returns {Promise<{msg: string, error: *}>}
*/
async getDevices() { async getDevices() {
const response = await this.makeRequest({ return this.makeRequest({
uri: '/user/device', uri: '/user/device',
qs: { lang: 'en', getTags: 1 }, qs: { lang: 'en', getTags: 1 },
}); });
return response;
} }
/**
* Get information about all associated devices to account
*
* @param deviceId
*
* @returns {Promise<{msg: string, error: *}>}
*/
async getDevice(deviceId) { async getDevice(deviceId) {
const response = await this.makeRequest({ return this.makeRequest({
uri: `/user/device/${deviceId}`, uri: `/user/device/${deviceId}`,
qs: { lang: 'en', getTags: 1 }, qs: { lang: 'en', getTags: 1 },
}); });
return response;
} }
/**
* 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) { async getDevicePowerState(deviceId, channel = 1) {
const device = await this.getDevice(deviceId); const device = await this.getDevice(deviceId);
const error = _get(device, 'error', false); const error = _get(device, 'error', false);
const switchesAmount = getDeviceChannelCount(device.uiid);
let state = _get(device, 'params.switch', false); let state = _get(device, 'params.switch', false);
const switches = _get(device, 'params.switches', false); const switches = _get(device, 'params.switches', false);
const switchesAmount = getDeviceChannelCount(device.uiid);
if (error || switchesAmount < channel || (!state && !switches)) { if (error || switchesAmount < channel || (!state && !switches)) {
if (error && parseInt(error) === 401) { if (error && parseInt(error) === 401) {
@@ -130,12 +170,21 @@ class eWeLink {
return { status: 'ok', state }; return { status: 'ok', state };
} }
/**
* Change power state for a specific device
*
* @param deviceId
* @param state
* @param channel
*
* @returns {Promise<{state: *, status: string}|{msg: string, error: *}>}
*/
async setDevicePowerState(deviceId, state, channel = 1) { async setDevicePowerState(deviceId, state, channel = 1) {
const device = await this.getDevice(deviceId); const device = await this.getDevice(deviceId);
const error = _get(device, 'error', false); const error = _get(device, 'error', false);
const switchesAmount = getDeviceChannelCount(device.uiid);
const status = _get(device, 'params.switch', false); const status = _get(device, 'params.switch', false);
const switches = _get(device, 'params.switches', false); const switches = _get(device, 'params.switches', false);
const switchesAmount = getDeviceChannelCount(device.uiid);
if (error || switchesAmount < channel || (!status && !switches)) { if (error || switchesAmount < channel || (!status && !switches)) {
if (error && parseInt(error) === 401) { if (error && parseInt(error) === 401) {
@@ -144,46 +193,50 @@ class eWeLink {
return { error, msg: 'Device does not exist' }; return { error, msg: 'Device does not exist' };
} }
let stateToSwitch = state;
if (state === 'toggle') {
stateToSwitch = status === 'on' ? 'off' : 'on';
}
const params = {}; const params = {};
if (switches) { if (switches) {
params.switches = switches; params.switches = switches;
params.switches[channel - 1].switch = state; params.switches[channel - 1].switch = stateToSwitch;
} else { } else {
params.switch = state; params.switch = stateToSwitch;
} }
const payloadLogin = wssLoginPayload({ at: this.at, apiKey: this.apiKey }); const payloadLogin = payloads.wssLoginPayload({
const payloadUpdate = wssUpdatePayload({ at: this.at,
apiKey: this.apiKey,
});
const payloadUpdate = payloads.wssUpdatePayload({
apiKey: this.apiKey, apiKey: this.apiKey,
deviceId, deviceId,
params, params,
}); });
const wsp = new WebSocketAsPromised(this.getApiWebSocket(), { await WebSocketRequest(this.getApiWebSocket(), [
createWebSocket: url => new W3CWebSocket(url), payloadLogin,
}); payloadUpdate,
]);
await wsp.open();
await wsp.send(payloadLogin);
await delay(1000);
await wsp.send(payloadUpdate);
await wsp.close();
return { status: 'ok', state }; return { status: 'ok', state };
} }
/**
* Toggle power state for a specific device
*
* @param deviceId
* @param channel
*
* @returns {Promise<{state: *, status: string}|{msg: string, error: *}>}
*/
async toggleDevice(deviceId, channel = 1) { async toggleDevice(deviceId, channel = 1) {
const powerState = await this.getDevicePowerState(deviceId, channel); return this.setDevicePowerState(deviceId, 'toggle', channel);
const state = _get(powerState, 'state', false);
if (!state) {
return { error: powerState.error, msg: 'Device does not exist' };
}
const newState = state === 'on' ? 'off' : 'on';
return this.setDevicePowerState(deviceId, newState, channel);
} }
} }

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "ewelink-api", "name": "ewelink-api",
"version": "1.2.1", "version": "1.2.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "ewelink-api", "name": "ewelink-api",
"version": "1.2.1", "version": "1.2.2",
"description": "eWeLink API for Node.js", "description": "eWeLink API for Node.js",
"author": "Martín M.", "author": "Martín M.",
"license": "MIT", "license": "MIT",