mirror of
https://github.com/skydiver/ewelink-api.git
synced 2025-12-21 21:33:11 +01:00
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:
@@ -1,6 +1,5 @@
|
||||
const crypto = require('crypto');
|
||||
const random = require('random');
|
||||
const nonce = require('nonce')();
|
||||
|
||||
const makeFakeIMEI = () => {
|
||||
const num1 = random.int(1000, 9999);
|
||||
@@ -8,55 +7,6 @@ const makeFakeIMEI = () => {
|
||||
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 =>
|
||||
crypto
|
||||
.createHmac('sha256', '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM')
|
||||
@@ -153,8 +103,6 @@ const getDeviceChannelCount = deviceUUID => {
|
||||
|
||||
module.exports = {
|
||||
makeAuthorizationSign,
|
||||
loginPayload,
|
||||
wssLoginPayload,
|
||||
wssUpdatePayload,
|
||||
makeFakeIMEI,
|
||||
getDeviceChannelCount,
|
||||
};
|
||||
|
||||
9
lib/payloads/index.js
Normal file
9
lib/payloads/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const loginPayload = require('./loginPayload');
|
||||
const wssLoginPayload = require('./wssLoginPayload');
|
||||
const wssUpdatePayload = require('./wssUpdatePayload');
|
||||
|
||||
module.exports = {
|
||||
loginPayload,
|
||||
wssLoginPayload,
|
||||
wssUpdatePayload,
|
||||
};
|
||||
19
lib/payloads/loginPayload.js
Normal file
19
lib/payloads/loginPayload.js
Normal 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;
|
||||
24
lib/payloads/wssLoginPayload.js
Normal file
24
lib/payloads/wssLoginPayload.js
Normal 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;
|
||||
15
lib/payloads/wssUpdatePayload.js
Normal file
15
lib/payloads/wssUpdatePayload.js
Normal 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
34
lib/websocket.js
Normal 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
125
main.js
@@ -1,18 +1,15 @@
|
||||
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 {
|
||||
makeAuthorizationSign,
|
||||
loginPayload,
|
||||
wssLoginPayload,
|
||||
wssUpdatePayload,
|
||||
getDeviceChannelCount,
|
||||
} = require('./lib/ewelink-helper');
|
||||
|
||||
const payloads = require('./lib/payloads');
|
||||
|
||||
class eWeLink {
|
||||
constructor({ region = 'us', email, password, at, apiKey }) {
|
||||
if (!at && (!email && !password)) {
|
||||
@@ -26,14 +23,34 @@ class eWeLink {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate eWeLink API URL
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getApiUrl() {
|
||||
return `https://${this.region}-api.coolkit.cc:8080/api`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate eWeLink WebSocket URL
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getApiWebSocket() {
|
||||
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 = {} }) {
|
||||
const { at } = this;
|
||||
|
||||
@@ -58,8 +75,13 @@ class eWeLink {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to login into eWeLink API
|
||||
*
|
||||
* @returns {Promise<{msg: string, error: *}>}
|
||||
*/
|
||||
async login() {
|
||||
const body = loginPayload({
|
||||
const body = payloads.loginPayload({
|
||||
email: this.email,
|
||||
password: this.password,
|
||||
});
|
||||
@@ -93,28 +115,46 @@ class eWeLink {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific device information
|
||||
*
|
||||
* @returns {Promise<{msg: string, error: *}>}
|
||||
*/
|
||||
async getDevices() {
|
||||
const response = await this.makeRequest({
|
||||
return this.makeRequest({
|
||||
uri: '/user/device',
|
||||
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) {
|
||||
const response = await this.makeRequest({
|
||||
return this.makeRequest({
|
||||
uri: `/user/device/${deviceId}`,
|
||||
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) {
|
||||
const device = await this.getDevice(deviceId);
|
||||
const error = _get(device, 'error', false);
|
||||
const switchesAmount = getDeviceChannelCount(device.uiid);
|
||||
let state = _get(device, 'params.switch', false);
|
||||
const switches = _get(device, 'params.switches', false);
|
||||
const switchesAmount = getDeviceChannelCount(device.uiid);
|
||||
|
||||
if (error || switchesAmount < channel || (!state && !switches)) {
|
||||
if (error && parseInt(error) === 401) {
|
||||
@@ -130,12 +170,21 @@ class eWeLink {
|
||||
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) {
|
||||
const device = await this.getDevice(deviceId);
|
||||
const error = _get(device, 'error', false);
|
||||
const switchesAmount = getDeviceChannelCount(device.uiid);
|
||||
const status = _get(device, 'params.switch', false);
|
||||
const switches = _get(device, 'params.switches', false);
|
||||
const switchesAmount = getDeviceChannelCount(device.uiid);
|
||||
|
||||
if (error || switchesAmount < channel || (!status && !switches)) {
|
||||
if (error && parseInt(error) === 401) {
|
||||
@@ -144,46 +193,50 @@ class eWeLink {
|
||||
return { error, msg: 'Device does not exist' };
|
||||
}
|
||||
|
||||
let stateToSwitch = state;
|
||||
|
||||
if (state === 'toggle') {
|
||||
stateToSwitch = status === 'on' ? 'off' : 'on';
|
||||
}
|
||||
|
||||
const params = {};
|
||||
|
||||
if (switches) {
|
||||
params.switches = switches;
|
||||
params.switches[channel - 1].switch = state;
|
||||
params.switches[channel - 1].switch = stateToSwitch;
|
||||
} else {
|
||||
params.switch = state;
|
||||
params.switch = stateToSwitch;
|
||||
}
|
||||
|
||||
const payloadLogin = wssLoginPayload({ at: this.at, apiKey: this.apiKey });
|
||||
const payloadUpdate = wssUpdatePayload({
|
||||
const payloadLogin = payloads.wssLoginPayload({
|
||||
at: this.at,
|
||||
apiKey: this.apiKey,
|
||||
});
|
||||
|
||||
const payloadUpdate = payloads.wssUpdatePayload({
|
||||
apiKey: this.apiKey,
|
||||
deviceId,
|
||||
params,
|
||||
});
|
||||
|
||||
const wsp = new WebSocketAsPromised(this.getApiWebSocket(), {
|
||||
createWebSocket: url => new W3CWebSocket(url),
|
||||
});
|
||||
|
||||
await wsp.open();
|
||||
await wsp.send(payloadLogin);
|
||||
await delay(1000);
|
||||
await wsp.send(payloadUpdate);
|
||||
await wsp.close();
|
||||
await WebSocketRequest(this.getApiWebSocket(), [
|
||||
payloadLogin,
|
||||
payloadUpdate,
|
||||
]);
|
||||
|
||||
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) {
|
||||
const powerState = await this.getDevicePowerState(deviceId, 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);
|
||||
return this.setDevicePowerState(deviceId, 'toggle', channel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ewelink-api",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ewelink-api",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "eWeLink API for Node.js",
|
||||
"author": "Martín M.",
|
||||
"license": "MIT",
|
||||
|
||||
Reference in New Issue
Block a user