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 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
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 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
2
package-lock.json
generated
@@ -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": {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user